Exemplo n.º 1
0
    def test_get_training_attribute(self):
        cfg = {
            "trainings": {
                "fallback": {
                    "query": {
                        "bpm": "120..",
                    },
                    "target": "MPD1",
                },
                "10K": {
                    "query": {
                        "bpm": "180..",
                        "length": "60..240",
                    },
                    "use_flavours": ["f1", "f2"],
                }
            }
        }
        config = get_plugin_configuration(cfg)
        training = config["trainings"]["10K"]

        # Direct
        self.assertEqual(cfg["trainings"]["10K"]["query"],
                         common.get_training_attribute(training, "query"))
        self.assertEqual(
            cfg["trainings"]["10K"]["use_flavours"],
            common.get_training_attribute(training, "use_flavours"))

        # Fallback
        self.assertEqual(cfg["trainings"]["fallback"]["target"],
                         common.get_training_attribute(training, "target"))

        # Inexistent
        self.assertIsNone(common.get_training_attribute(training, "hoppa"))
Exemplo n.º 2
0
    def list_training_attributes(self, training_name: str):
        if not self.config["trainings"].exists() or not \
                self.config["trainings"][training_name].exists():
            self._say("Training[{0}] does not exist.".format(training_name),
                      is_error=True)
            return

        # Just output the list of the names of the available trainings
        if not self.verbose_log:
            self._say("{0}".format(training_name), log_only=False)
            return

        display_name = "[   {}   ]".format(training_name)
        self._say("\n{0}".format(display_name.center(80, "=")), log_only=False)

        training: Subview = self.config["trainings"][training_name]
        training_keys = list(
            set(common.MUST_HAVE_TRAINING_KEYS) | set(training.keys()))
        final_keys = [
            "duration", "query", "use_flavours", "combined_query", "ordering",
            "target"
        ]
        final_keys.extend(tk for tk in training_keys if tk not in final_keys)

        for key in final_keys:
            val = common.get_training_attribute(training, key)

            # Handle non-existent (made up) keys
            if key == "combined_query" and common.get_training_attribute(
                    training, "use_flavours"):
                val = self._gather_query_elements(training)

            if val is None:
                continue

            if key == "duration":
                val = common.get_human_readable_time(val * 60)
            elif key == "ordering":
                val = dict(val)
            elif key == "query":
                pass

            if isinstance(val, dict):
                value = []
                for k in val:
                    value.append("{key}({val})".format(key=k, val=val[k]))
                val = ", ".join(value)

            self._say("{0}: {1}".format(key, val), log_only=False)
Exemplo n.º 3
0
def get_items_for_duration(training: Subview, items, duration):
    """Returns the items picked by the Picker strategy specified bu the
        `pick_strategy` key
        """
    picker = common.get_training_attribute(training, "pick_strategy")
    if not picker or picker not in pickers:
        picker = default_picker

    picker_info = pickers[picker]
    instance: BasePicker = common.get_class_instance(picker_info["module"],
                                                     picker_info["class"])
    instance.setup(training, items, duration)
    return instance.get_picked_items()
Exemplo n.º 4
0
    def _gather_query_elements(self, training: Subview):
        """Sum all query elements into one big list ordered from strongest to
        weakest: command -> training -> flavours
        """
        command_query = self.query
        training_query = []
        flavour_query = []

        # Append the query elements from the configuration
        tconf = common.get_training_attribute(training, "query")
        if tconf:
            for key in tconf.keys():
                nqe = common.get_normalized_query_element(key, tconf.get(key))
                if type(nqe) == list:
                    training_query.extend(nqe)
                else:
                    training_query.append(nqe)

        # Append the query elements from the flavours defined on the training
        flavours = common.get_training_attribute(training, "use_flavours")
        if flavours:
            flavours = [flavours] if type(flavours) == str else flavours
            for flavour_name in flavours:
                flavour: Subview = self.config["flavours"][flavour_name]
                flavour_query += common.get_flavour_elements(flavour)

        self._say("Command query elements: {}".format(command_query),
                  log_only=True)
        self._say("Training query elements: {}".format(training_query),
                  log_only=True)
        self._say("Flavour query elements: {}".format(flavour_query),
                  log_only=True)

        raw_combined_query = command_query + training_query + flavour_query

        self._say("Combined query elements: {}".format(raw_combined_query),
                  log_only=True)

        return raw_combined_query
Exemplo n.º 5
0
def get_ordered_items(training: Subview, items):
    """Returns the items ordered by the strategy specified in the
    `ordering_strategy` key
    """
    strategy = common.get_training_attribute(training, "ordering_strategy")
    if not strategy or strategy not in permutations:
        strategy = default_strategy

    perm = permutations[strategy]
    instance: BasePermutation = common.get_class_instance(
        perm["module"], perm["class"])
    instance.setup(training, items)
    return instance.get_ordered_items()
    def _clean_target(self):
        training_name = self._get_cleaned_training_name()
        target_name = common.get_training_attribute(self.training, "target")
        clean_target = common.get_target_attribute_for_training(
            self.training, "clean_target")

        if clean_target is False:
            return

        dst_path = Path(common.get_destination_path_for_training(
            self.training))

        # Clean entire target
        dst_sub_dir = dst_path

        # Only clean specific training folder
        if clean_target == "training":
            dst_sub_dir = dst_path.joinpath(training_name)

        common.say("Cleaning target[{0}]: {1}".format(target_name,
                                                      dst_sub_dir))

        if os.path.isdir(dst_sub_dir):
            shutil.rmtree(dst_sub_dir)
            os.mkdir(dst_sub_dir)

        # Clean additional files
        additional_files = common.get_target_attribute_for_training(
            self.training, "delete_from_device")
        if additional_files and len(additional_files) > 0:
            root = common.get_target_attribute_for_training(
                self.training, "device_root")
            root = Path(root).expanduser()

            common.say(
                "Deleting additional files: {0}".format(additional_files))

            for path in additional_files:
                path = Path(str.strip(path, "/"))
                dst_path = os.path.realpath(root.joinpath(path))

                if not os.path.isfile(dst_path):
                    common.say(
                        "The file to delete does not exist: {0}".format(path),
                        log_only=True)
                    continue

                common.say("Deleting: {}".format(dst_path))
                if not self.cfg_dry_run:
                    os.remove(dst_path)
Exemplo n.º 7
0
    def _build_order_info(self):
        cfg_ordering = common.get_training_attribute(self.training, "ordering")
        fields = []
        if cfg_ordering:
            fields = list(cfg_ordering.keys())

        default_field_data = {
            "min": 99999999.9,
            "max": 0.0,
            "delta": 0.0,
            "step": 0.0,
            "weight": 100
        }

        # Build Order Info dictionary
        self.order_info = {}
        for field in fields:
            field_name = field.strip()
            self.order_info[field_name] = default_field_data.copy()
            self.order_info[field_name]["weight"] = cfg_ordering[field]

        # Populate Order Info
        for field_name in self.order_info.keys():
            field_info = self.order_info[field_name]
            _min, _max, _sum, _avg = common.get_min_max_sum_avg_for_items(
                self.items, field_name)
            field_info["min"] = _min
            field_info["max"] = _max

        # Calculate other values in Order Info
        for field_name in self.order_info.keys():
            field_info = self.order_info[field_name]
            field_info["delta"] = round(field_info["max"] - field_info["min"],
                                        6)
            if field_info["delta"] > 0:
                field_info["step"] = round(100 / field_info["delta"], 6)
            else:
                field_info["step"] = 0

        common.say("ORDER INFO: {0}".format(self.order_info))
    def _generate_playist(self):
        training_name = self._get_cleaned_training_name()
        playlist_name = self._get_training_name()
        target_name = common.get_training_attribute(self.training, "target")

        if not common.get_target_attribute_for_training(
                self.training, "generate_playlist"):
            common.say("Playlist generation to target[{0}] was skipped "
                       "(generate_playlist=no).".format(target_name),
                       log_only=False)
            return

        dst_path = Path(common.get_destination_path_for_training(
            self.training))
        dst_sub_dir = dst_path.joinpath(training_name)
        playlist_filename = "{}.m3u".format(playlist_name)
        dst = dst_sub_dir.joinpath(playlist_filename)

        lines = [
            "# Playlist generated for training '{}' on {}". \
                format(training_name, datetime.now())
        ]

        for item in self.items:
            path = util.displayable_path(
                item.get("exportpath", item.get("path")))
            if path:
                path = util.syspath(path)
                line = "{path}".format(path=path)
                lines.append(line)

        with tempfile.NamedTemporaryFile(mode='w+b', delete=False) as ntf:
            tmp_playlist = ntf.name
            for line in lines:
                ntf.write("{}\n".format(line).encode("utf-8"))

        common.say("Created playlist: {0}".format(dst), log_only=True)
        util.copy(tmp_playlist, dst)
        util.remove(tmp_playlist)
Exemplo n.º 9
0
    def handle_training(self):
        training_name = self.query.pop(0)
        training: Subview = self.config["trainings"][training_name]

        self._say("Handling training: {0}".format(training_name),
                  log_only=False)

        # todo: create a sanity checker for training to check all attributes
        if not training.exists():
            self._say("There is no training with this name[{0}]!".format(
                training_name),
                      log_only=False)
            return

        # Verify target device path path
        if not common.get_destination_path_for_training(training):
            self._say("Invalid target!", log_only=False)
            return

        # Get the library items
        lib_items: Results = self._retrieve_library_items(training)

        # Show count only
        if self.cfg_count:
            self._say("Number of songs available: {}".format(len(lib_items)),
                      log_only=False)
            return

        # Check count
        if len(lib_items) < 1:
            self._say("No songs in your library match this training!",
                      log_only=False)
            return

        duration = common.get_training_attribute(training, "duration")
        if not duration:
            self._say("There is no duration set for the selected training!",
                      log_only=False)
            return

        # 1) order items by `ordering_strategy`
        sorted_items = itemorder.get_ordered_items(training, lib_items)
        # flds = ["ordering_score", "artist", "title"]
        # self.display_library_items(sorted_items, flds, prefix="SORTED: ")

        # 2) select items that cover the training duration
        itempick.favour_unplayed = \
            common.get_training_attribute(training, "favour_unplayed")
        sel_items = itempick.get_items_for_duration(training, sorted_items,
                                                    duration * 60)

        # 3) Show some info
        total_time = common.get_duration_of_items(sel_items)
        self._say("Available songs: {}".format(len(lib_items)))
        self._say("Selected songs: {}".format(len(sel_items)))
        self._say("Planned training duration: {0}".format(
            common.get_human_readable_time(duration * 60)))
        self._say("Total song duration: {}".format(
            common.get_human_readable_time(total_time)))

        # 4) Show the selected songs
        flds = self._get_training_query_element_keys(training)
        flds += ["play_count", "artist", "title"]
        self.display_library_items(sel_items, flds, prefix="Selected: ")

        # 5) Clean, Copy, Playlist, Run
        itemexport.generate_output(training, sel_items, self.cfg_dry_run)
        self._say("Run!", log_only=False)
Exemplo n.º 10
0
    def _copy_items(self):
        training_name = self._get_cleaned_training_name()
        target_name = common.get_training_attribute(self.training, "target")

        # The copy_files is only False when it is explicitly declared so
        copy_files = common.get_target_attribute_for_training(
            self.training, "copy_files")
        copy_files = False if copy_files == False else True

        if not copy_files:
            common.say(
                "Copying to target[{0}] was skipped (copy_files=no).".format(
                    target_name))
            return

        increment_play_count = common.get_training_attribute(
            self.training, "increment_play_count")
        dst_path = Path(common.get_destination_path_for_training(
            self.training))

        dst_sub_dir = dst_path.joinpath(training_name)
        if not os.path.isdir(dst_sub_dir):
            os.mkdir(dst_sub_dir)

        common.say("Copying to target[{0}]: {1}".format(
            target_name, dst_sub_dir))

        cnt = 0
        # todo: disable alive bar when running in verbose mode
        # from beets import logging as beetslogging
        # beets_log = beetslogging.getLogger("beets")
        # print(beets_log.getEffectiveLevel())

        with alive_bar(len(self.items)) as bar:
            for item in self.items:
                src = util.displayable_path(item.get("path"))
                if not os.path.isfile(src):
                    # todo: this is bad enough to interrupt! create option
                    # for this
                    common.say("File does not exist: {}".format(src))
                    continue

                fn, ext = os.path.splitext(src)
                gen_filename = "{0}_{1}{2}" \
                    .format(str(cnt).zfill(6), common.get_random_string(), ext)

                dst = dst_sub_dir.joinpath(gen_filename)
                # dst = "{0}/{1}".format(dst_path, gen_filename)

                common.say("Copying[{1}]: {0}".format(src, gen_filename))

                if not self.cfg_dry_run:
                    util.copy(src, dst)

                    # store the file_name for the playlist
                    item["exportpath"] = util.bytestring_path(gen_filename)

                    if increment_play_count:
                        common.increment_play_count_on_item(item)

                cnt += 1
                bar()