Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions backend/beets_flask/server/routes/library/artwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from io import BytesIO
from typing import TYPE_CHECKING, cast

from beets import util as beets_util
from mediafile import Image, MediaFile # comes with the beets install
from PIL import Image as PILImage
from quart import (
Expand All @@ -22,6 +21,7 @@
InvalidUsageException,
NotFoundException,
)
from beets_flask.server.routes.library.path_utils import resolve_library_path

if TYPE_CHECKING:
# For type hinting the global g object
Expand Down Expand Up @@ -112,7 +112,7 @@ async def item_art_idx(item_id: int):
f"Item with beets_id:'{item_id}' not found in beets db."
)

item_path = beets_util.syspath(item.path)
item_path = resolve_library_path(item.path)
count = get_image_count_from_file(item_path)
return jsonify({"count": count}), 200

Expand All @@ -129,7 +129,7 @@ async def item_art(item_id: int):
f"Item with beets_id:'{item_id}' not found in beets db."
)

item_path = beets_util.syspath(item.path)
item_path = resolve_library_path(item.path)
img_data = get_image_data_from_file(item_path, idx)
return await send_image(img_data, size)

Expand All @@ -151,7 +151,7 @@ async def album_art(album_id: int):

# Has art set on album level
if album.artpath and idx == 0:
art_path = beets_util.syspath(album.artpath)
art_path = resolve_library_path(album.artpath)
if not os.path.exists(art_path):
raise IntegrityException(
f"Album art file '{art_path}' does not exist for album beets_id:'{album_id}'."
Expand Down
6 changes: 3 additions & 3 deletions backend/beets_flask/server/routes/library/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@

import aiofiles
import numpy as np
from beets import util as beets_util
from cachetools import Cache, TTLCache
from cachetools.keys import hashkey
from quart import Blueprint, Response, g

from beets_flask.logger import log
from beets_flask.server.exceptions import IntegrityException, NotFoundException
from beets_flask.server.routes.library.path_utils import resolve_library_path

audio_bp = Blueprint("audio", __name__)

Expand All @@ -45,7 +45,7 @@ async def item_audio(item_id: int):
f"Item with beets_id:'{item_id}' not found in beets db."
)

item_path = beets_util.syspath(item.path)
item_path = resolve_library_path(item.path)
if not os.path.exists(item_path):
raise IntegrityException(
f"Item file '{item_path}' does not exist for item beets_id:'{item_id}'."
Expand All @@ -70,7 +70,7 @@ async def item_audio_peaks(item_id: int):
f"Item with beets_id:'{item_id}' not found in beets db."
)

item_path = beets_util.syspath(item.path)
item_path = resolve_library_path(item.path)
if not os.path.exists(item_path):
raise IntegrityException(
f"Item file '{item_path}' does not exist for item beets_id:'{item_id}'."
Expand Down
4 changes: 2 additions & 2 deletions backend/beets_flask/server/routes/library/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from pathlib import Path
from typing import TYPE_CHECKING

from beets import util as beets_util
from quart import Blueprint, g
from tinytag import TinyTag

from beets_flask.server.exceptions import IntegrityException, NotFoundException
from beets_flask.server.routes.library.path_utils import resolve_library_path

if TYPE_CHECKING:
# For type hinting the global g object
Expand All @@ -36,7 +36,7 @@ async def item_metadata(item_id: int):
)

# File path
item_path = beets_util.syspath(item.path)
item_path = resolve_library_path(item.path)
if not os.path.exists(item_path):
raise IntegrityException(
f"Item file '{item_path}' does not exist for item beets_id:'{item_id}'."
Expand Down
45 changes: 45 additions & 0 deletions backend/beets_flask/server/routes/library/path_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Helpers for resolving paths stored in the beets library.

Beets can store item and artwork paths relative to the configured library
``directory`` when the files live inside that directory. Beets itself expands
those paths through its library abstractions, but beets-flask sometimes needs a
plain filesystem path for libraries such as mutagen/mediafile, TinyTag, ffmpeg,
or ``os.path.getsize``.

This module provides a single helper that mirrors Beets' expected behavior for
those call sites: absolute paths are returned unchanged; relative paths are
resolved against the currently-open Beets library directory.
"""

import os
from os import PathLike
from typing import TYPE_CHECKING

from beets import util as beets_util
from quart import g

if TYPE_CHECKING:
from . import g


def resolve_library_path(path: str | bytes | PathLike[str] | PathLike[bytes]) -> str:
"""Return an absolute filesystem path for a Beets item/artwork path.

Beets commonly stores paths relative to ``Library.directory``. Directly
passing such a path to file APIs makes it resolve relative to the current
process working directory, which is not necessarily the music library root
inside the beets-flask container. Resolve relative paths against the
active Beets library directory so existing Beets databases with relative
paths work correctly.
"""

filesystem_path = beets_util.syspath(path)
if os.path.isabs(filesystem_path):
return filesystem_path

lib = getattr(g, "lib", None)
library_directory = getattr(lib, "directory", None)
if library_directory:
return os.path.join(beets_util.syspath(library_directory), filesystem_path)

return filesystem_path
5 changes: 3 additions & 2 deletions backend/beets_flask/server/routes/library/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

from beets_flask.config import get_config
from beets_flask.logger import log
from beets_flask.server.routes.library.path_utils import resolve_library_path
from beets_flask.server.exceptions import NotFoundException
from beets_flask.server.routes.exception import InvalidUsageException
from beets_flask.server.utility import pop_query_param
Expand Down Expand Up @@ -636,7 +637,7 @@ def _repr_Item(item: Item | None, minimal=False) -> ItemResponse | ItemResponseM
]
else:
# Use all keys
keys = item.keys(True) + ["name"]
keys = [k for k in item.keys(True) if k != "filesize"] + ["name"]

# Check data source prefixes:
# plugins such as spotify, tidal, discogs add a prefix to the id,
Expand Down Expand Up @@ -722,7 +723,7 @@ def _repr_Item(item: Item | None, minimal=False) -> ItemResponse | ItemResponseM
# Get the size (in bytes) of the backing file. This is useful
# for the Tomahawk resolver API.
try:
out["size"] = os.path.getsize(beets_util.syspath(path=item.path))
out["size"] = os.path.getsize(resolve_library_path(item.path))
except OSError:
out["size"] = 0

Expand Down
25 changes: 13 additions & 12 deletions frontend/src/components/library/coverArt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,18 +182,19 @@ function CoverArtFromQuery({
}

if (isError) {
if (error instanceof HTTPError) {
return (
<CoverArtError
sx={coverSx}
error={error}
size={size}
{...props}
/>
);
} else {
throw error;
}
const coverArtError =
error instanceof HTTPError
? error
: new HTTPError(error?.message || String(error));

return (
<CoverArtError
sx={coverSx}
error={coverArtError}
size={size}
{...props}
/>
);
}

if (art) {
Expand Down
Loading