Readded licence. Removed last legacy occurrences of artist and

replaced them with `field`. Removed unnecessary default parameters where
applicable.
This commit is contained in:
Sebastian Mohr 2026-01-07 15:57:48 +01:00 committed by Šarūnas Nejus
parent 5ed0a72310
commit 6c52252672
No known key found for this signature in database
2 changed files with 34 additions and 17 deletions

View file

@ -1,17 +1,31 @@
"""Get a random song or album from the library."""
# This file is part of beets.
# Copyright 2016, Philippe Mongeau.
# Copyright 2025, Sebastian Mohr.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
from __future__ import annotations
import random
from itertools import groupby, islice
from operator import attrgetter
from typing import TYPE_CHECKING, Any, Iterable
from typing import TYPE_CHECKING, Any
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand, print_
if TYPE_CHECKING:
import optparse
from collections.abc import Iterable
from beets.library import LibModel, Library
@ -24,10 +38,10 @@ def random_func(lib: Library, opts: optparse.Values, args: list[str]):
# Print a random subset.
for obj in random_objs(
objs=objs,
equal_chance_field=opts.field,
number=opts.number,
time_minutes=opts.time,
equal_chance=opts.equal_chance,
equal_chance_field=opts.field,
):
print_(format(obj))
@ -45,7 +59,7 @@ random_cmd.parser.add_option(
"-e",
"--equal-chance",
action="store_true",
help="each artist has the same chance",
help="each field has the same chance",
)
random_cmd.parser.add_option(
"-t",
@ -55,10 +69,10 @@ random_cmd.parser.add_option(
help="total length in minutes of objects to choose",
)
random_cmd.parser.add_option(
"-f",
"--field",
action="store",
type="string",
default="albumartist",
help="field to use for equal chance sampling (default: albumartist)",
)
random_cmd.parser.add_all_common_options()
@ -74,13 +88,13 @@ NOT_FOUND_SENTINEL = object()
def _equal_chance_permutation(
objs: Iterable[LibModel], field: str = "albumartist"
objs: Iterable[LibModel], field: str
) -> Iterable[LibModel]:
"""Generate (lazily) a permutation of the objects where every group
with equal values for `field` have an equal chance of appearing in
any given position.
"""
# Group the objects by artist so we can sample from them.
# Group the objects by field so we can sample from them.
key = attrgetter(field)
def get_attr(obj: LibModel) -> Any:
@ -126,10 +140,10 @@ def _take_time(
def random_objs(
objs: Iterable[LibModel],
equal_chance_field: str,
number: int = 1,
time_minutes: float | None = None,
equal_chance: bool = False,
equal_chance_field: str = "albumartist",
) -> Iterable[LibModel]:
"""Get a random subset of items, optionally constrained by time or count.
@ -138,15 +152,14 @@ def random_objs(
- number: The number of objects to select.
- time_minutes: If specified, the total length of selected objects
should not exceed this many minutes.
- equal_chance: If True, each artist has the same chance of being
- equal_chance: If True, each field has the same chance of being
selected, regardless of how many tracks they have.
- random_gen: An optional random generator to use for shuffling.
"""
# Permute the objects either in a straightforward way or an
# artist-balanced way.
perm: Iterable[LibModel]
# field-balanced way.
if equal_chance:
perm = _equal_chance_permutation(objs, field=equal_chance_field)
perm = _equal_chance_permutation(objs, equal_chance_field)
else:
perm = list(objs)
random.shuffle(perm)

View file

@ -140,13 +140,15 @@ class TestRandomObjs:
def test_random_selection_by_count(self):
"""Test selecting a specific number of items."""
selected = list(random_objs(self.items, number=2))
selected = list(random_objs(self.items, "artist", number=2))
assert len(selected) == 2
assert all(item in self.items for item in selected)
def test_random_selection_by_time(self):
"""Test selecting items constrained by total time (minutes)."""
selected = list(random_objs(self.items, time_minutes=6)) # 6 minutes
selected = list(
random_objs(self.items, "artist", time_minutes=6)
) # 6 minutes
total_time = (
sum(item.length for item in selected) / 60
) # Convert to minutes
@ -160,7 +162,9 @@ class TestRandomObjs:
helper.create_item(artist=self.artist1, length=180)
)
selected = list(random_objs(self.items, number=10, equal_chance=True))
selected = list(
random_objs(self.items, "artist", number=10, equal_chance=True)
)
artist_counts = {}
for item in selected:
artist_counts[item.artist] = artist_counts.get(item.artist, 0) + 1
@ -170,11 +174,11 @@ class TestRandomObjs:
def test_empty_input_list(self):
"""Test behavior with an empty input list."""
selected = list(random_objs([], number=1))
selected = list(random_objs([], "artist", number=1))
assert len(selected) == 0
def test_no_constraints_returns_all(self):
"""Test that no constraints return all items in random order."""
selected = list(random_objs(self.items, 3))
selected = list(random_objs(self.items, "artist", number=3))
assert len(selected) == len(self.items)
assert set(selected) == set(self.items)