# After update on Debian trixie mopidy-local has an error

**URL:** <https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139>\
**Category:** Help and support\
**Created:** [July 4, 2025, 1:41pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139 "2025-07-04T13:41:00Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![David](https://avatars.discourse-cdn.com/v4/letter/d/aeb1de/32.png) [@David](https://discourse.mopidy.com/u/David)\
**Post date:** [July 4, 2025, 1:41pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/1 "2025-07-04T13:41:01Z")

</div>

Hi

After updating my server to Debian trixie, mopidy-local has an error. Mopidy is still working. Am I too early for an update?

This command: `sudo journalctl -u mopidy`

has related to the problem the same output like: `sudo mopidyctl config`

```auto
Running "/usr/bin/mopidy --config /usr/share/mopidy/conf.d:/etc/mopidy/mopidy.conf config" as user mopidy
ERROR [MainThread] mopidy.ext Setup of extension from entry point local failed, ignoring extension.
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/mopidy/ext.py", line 250, in load_extensions
    command=extension.get_command(),
            ~~~~~~~~~~~~~~~~~~~~~ ^^
  File "/usr/lib/python3/dist-packages/mopidy_local/ __init__.py", line 45, in get_command
    from .commands import LocalCommand
  File "/usr/lib/python3/dist-packages/mopidy_local/commands.py", line 8, in <module>
    from mopidy_local import mtimes, storage, translator
  File "/usr/lib/python3/dist-packages/mopidy_local/storage.py", line 2, in <module>
    import imghdr
ModuleNotFoundError: No module named 'imghdr'
WARNING [MainThread] mopidy.config Ignoring config section 'local' because no matching extension was found
[core]
cache_dir = /var/cache/mopidy
config_dir = /etc/mopidy
data_dir = /var/lib/mopidy
max_tracklist_length = 10000
restore_state = false

[logging]
verbosity = 0
format = %(levelname)-8s [%(threadName)s] %(name)s %(message)s
color = false
config_file = 

[audio]
mixer = software
mixer_volume = 50
output = autoaudiosink
buffer_time = 

[proxy]
scheme = 
hostname = 
port = 
username = 
password = 

[file]
enabled = true
media_dirs = 
  /media/server1/FreedomBox/shares/group_share/Musik (Mopidy)
excluded_file_extensions = 
  .directory
  .html
  .jpeg
  .jpg
  .log
  .nfo
  .pdf
  .png
  .txt
  .zip
show_dotfiles = false
follow_symlinks = false
metadata_timeout = 1000

[http]
enabled = true
hostname = ::
port = 6680
zeroconf = Mopidy HTTP server on $hostname
allowed_origins = 
csrf_protection = true
default_app = mopidy

[m3u]
enabled = true
base_dir = 
default_encoding = latin-1
default_extension = .m3u8
playlists_dir = 

[softwaremixer]
enabled = true

[stream]
enabled = true
protocols = 
  http
  https
  mms
  rtmp
  rtmps
  rtsp
metadata_blacklist = 
timeout = 5000

[mpd]
enabled = true
hostname = ::
port = 6600
password = 
max_connections = 20
connection_timeout = 60
zeroconf = Mopidy MPD server on $hostname
command_blacklist = 
  listall
  listallinfo
default_playlist_scheme = m3u

```

- Installed dependencies: `sudo mopidyctl deps`

```auto
Running "/usr/bin/mopidy --config /usr/share/mopidy/conf.d:/etc/mopidy/mopidy.conf deps" as user mopidy
ERROR [MainThread] mopidy.ext Setup of extension from entry point local failed, ignoring extension.
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/mopidy/ext.py", line 250, in load_extensions
    command=extension.get_command(),
            ~~~~~~~~~~~~~~~~~~~~~ ^^
  File "/usr/lib/python3/dist-packages/mopidy_local/ __init__.py", line 45, in get_command
    from .commands import LocalCommand
  File "/usr/lib/python3/dist-packages/mopidy_local/commands.py", line 8, in <module>
    from mopidy_local import mtimes, storage, translator
  File "/usr/lib/python3/dist-packages/mopidy_local/storage.py", line 2, in <module>
    import imghdr
ModuleNotFoundError: No module named 'imghdr'
WARNING [MainThread] mopidy.config Ignoring config section 'local' because no matching extension was found
Executable: /usr/bin/mopidy
Platform: Linux-6.1.0-37-armmp-lpae-armv7l-with-glibc2.41
Python: CPython 3.13.3 from /usr/lib/python3.13
Mopidy: 3.4.2 from /usr/lib/python3/dist-packages
Mopidy-Local: 3.2.1 from /usr/lib/python3/dist-packages
Mopidy-MPD: 3.3.0 from /usr/lib/python3/dist-packages
GStreamer: 1.26.2.0 from /usr/lib/python3/dist-packages/gi
  Detailed information: 
    Python wrapper: python-gi 3.50.0
    Relevant elements:
      Found:
        uridecodebin
        souphttpsrc
        appsrc
        alsasink
        osssink
        oss4sink
        pulsesink
        id3demux
        id3v2mux
        lamemp3enc
        mpegaudioparse
        mpg123audiodec
        vorbisdec
        vorbisenc
        vorbisparse
        oggdemux
        oggmux
        oggparse
        flacdec
        flacparse
        shout2send
      Not found:
        flump3dec
        mad`

```

- Information about your computer’s hardware and software  
Hardware: Olimex Lime2  
Software: Debian armhf bookworm → trixie

If I am to early, no problem. I did the test with a dd copy of my root file system. So there is nothing lost…

Greetings from Switzerland

---

<div class="post-metadata">

**Author:** ![kingosticks](https://avatars.discourse-cdn.com/v4/letter/k/858c86/32.png) [@kingosticks](https://discourse.mopidy.com/u/kingosticks)\
**Post date:** [July 4, 2025, 3:28pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/2 "2025-07-04T15:28:24Z")

</div>

Short answer, yes. Mopidy-Local doesn’t support Python 13.3 due to [PEP 594 – Removing dead batteries from the standard library | peps.python.org](https://peps.python.org/pep-0594/#imghdr)

I think the workaround might be `sudo apt install python3-standard-imghdr`, can you try that? We need to fix Mopidy-Local. Weird nobody else (arch users?) flagged this sooner.

---

<div class="post-metadata">

**Author:** ![David](https://avatars.discourse-cdn.com/v4/letter/d/aeb1de/32.png) [@David](https://discourse.mopidy.com/u/David)\
**Post date:** [July 4, 2025, 6:00pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/3 "2025-07-04T18:00:29Z")

</div>

Thank you! I did install `sudo apt install python3-standard-imghdr` and after restarting the server the configuration didn’t show an error anymore. Then I did run `sudo mopidyctl local clear` and after this `sudo mopidyctl local scan` . And that’s what showed up in the terminal:

```auto
sudo mopidyctl local scan
Running "/usr/bin/mopidy --config /usr/share/mopidy/conf.d:/etc/mopidy/mopidy.conf local scan" as user mopidy
INFO [MainThread] mopidy. __main__ Starting Mopidy 3.4.2
INFO [MainThread] mopidy.config Loading config from builtin defaults
INFO [MainThread] mopidy.config Loading config from file:///usr/share/mopidy/conf.d/mopidy.conf
INFO [MainThread] mopidy.config Loading config from file:///usr/share/mopidy/conf.d/mopidy-local.conf
INFO [MainThread] mopidy.config Loading config from file:///etc/mopidy/mopidy.conf
INFO [MainThread] mopidy.config Loading config from command line options
INFO [MainThread] mopidy. __main__ Enabled extensions: http, local, mpd, stream, m3u, softwaremixer, file
INFO [MainThread] mopidy. __main__ Disabled extensions: none
INFO [MainThread] mopidy_local.commands Finding files in file:///media/server1/FreedomBox/shares/group_share/Musik%20%28Mopidy%29 ...
INFO [MainThread] mopidy_local.commands Found 3239 files in file:///media/server1/FreedomBox/shares/group_share/Musik%20%28Mopidy%29
INFO [MainThread] mopidy_local.commands Checking 200 tracks from library
INFO [MainThread] mopidy_local.commands Removing 0 missing tracks
INFO [MainThread] mopidy_local.commands Found 2801 tracks which need to be updated
INFO [MainThread] mopidy_local.commands Scanning...
ERROR [MainThread] mopidy. __main__'StructureWrapper' object has no attribute 'get_name'
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/mopidy/ __main__.py", line 143, in main
    return args.command.run(args, proxied_config)
           ~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/mopidy_local/commands.py", line 95, in run
    self._scan_metadata(
    ~~~~~~~~~~~~~~~~~~~ ^
        media_dir=media_dir,
        ^^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
        limit=args.limit,
        ^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/lib/python3/dist-packages/mopidy_local/commands.py", line 218, in _scan_metadata
    result = scanner.scan(file_uri)
  File "/usr/lib/python3/dist-packages/mopidy/audio/scan.py", line 70, in scan
    tags, mime, have_audio, duration = _process(pipeline, timeout)
                                       ~~~~~~~~ ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/mopidy/audio/scan.py", line 235, in _process
    mime = caps.get_structure(0).get_name()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'StructureWrapper' object has no attribute 'get_name'
Traceback (most recent call last):
  File "/usr/bin/mopidy", line 33, in <module>
    sys.exit(load_entry_point('Mopidy==3.4.2', 'console_scripts', 'mopidy')())
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^^
  File "/usr/lib/python3/dist-packages/mopidy/ __main__.py", line 143, in main
    return args.command.run(args, proxied_config)
           ~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/mopidy_local/commands.py", line 95, in run
    self._scan_metadata(
    ~~~~~~~~~~~~~~~~~~~ ^
        media_dir=media_dir,
        ^^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
        limit=args.limit,
        ^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/lib/python3/dist-packages/mopidy_local/commands.py", line 218, in _scan_metadata
    result = scanner.scan(file_uri)
  File "/usr/lib/python3/dist-packages/mopidy/audio/scan.py", line 70, in scan
    tags, mime, have_audio, duration = _process(pipeline, timeout)
                                       ~~~~~~~~ ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/mopidy/audio/scan.py", line 235, in _process
    mime = caps.get_structure(0).get_name()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'StructureWrapper' object has no attribute 'get_name'

```

What is causing this?  
FYI: I did a fresh trixie install with a new image and the same thing happened there, same error on mopidy-local

---

<div class="post-metadata">

**Author:** ![kingosticks](https://avatars.discourse-cdn.com/v4/letter/k/858c86/32.png) [@kingosticks](https://discourse.mopidy.com/u/kingosticks)\
**Post date:** [July 7, 2025, 8:49am UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/4 "2025-07-07T08:49:43Z")

</div>

A bug in newer release of GStreamer. See [add workaround for gstreamer 1.25.0 to 1.26.2 by girst · Pull Request #2200 · mopidy/mopidy · GitHub](https://github.com/mopidy/mopidy/pull/2200)

---

<div class="post-metadata">

**Author:** ![David](https://avatars.discourse-cdn.com/v4/letter/d/aeb1de/32.png) [@David](https://discourse.mopidy.com/u/David)\
**Post date:** [July 7, 2025, 5:50pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/5 "2025-07-07T17:50:12Z")

</div>

Thanks a lot @kingosticks! I deleted the existing `scan.py` at `/usr/lib/python3/dist-packages/mopidy/audio/scan.py` and copied the one from your mentioned workaround there. And it works 😃 Cool!!!

---

<div class="post-metadata">

**Author:** ![fmarzocca](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.mopidy.com/fmarzocca/32/1095_2.png) [@fmarzocca](https://discourse.mopidy.com/u/fmarzocca)\
**Post date:** [December 15, 2025, 5:56pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/6 "2025-12-15T17:56:28Z")

</div>

Hi David,

where did you get the correct scan.py?

---

<div class="post-metadata">

**Author:** ![David](https://avatars.discourse-cdn.com/v4/letter/d/aeb1de/32.png) [@David](https://discourse.mopidy.com/u/David)\
**Post date:** [December 16, 2025, 6:34pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/7 "2025-12-16T18:34:54Z")

</div>

Hi

I wasn’t able to find the file scan.py on mopidy anymore. But I did save the file back then and did copy the content below. So you just need to copy the content into your file.

```auto
import logging
import time
from enum import IntEnum
from pathlib import Path
from typing import Any, NamedTuple, cast

from mopidy import exceptions
from mopidy.audio import tags as tags_lib
from mopidy.audio import utils
from mopidy.internal import log
from mopidy.internal.gi import Gst, GstPbutils

class GstElementFactoryListType(IntEnum):
    DECODER = 1 << 0
    AUDIO = 1 << 50
    DEMUXER = 1 << 5
    DEPAYLOADER = 1 << 8
    PARSER = 1 << 6

class GstAutoplugSelectResult(IntEnum):
    TRY = 0
    EXPOSE = 1
    SKIP = 2

class _Result(NamedTuple):
    uri: str
    tags: dict[str, Any]
    duration: int | None
    seekable: bool
    mime: str | None
    playable: bool

logger = logging.getLogger( __name__ )

def _trace(*args, **kwargs):
    logger.log(log.TRACE_LOG_LEVEL, *args, **kwargs)

# TODO: replace with a scan(uri, timeout=1000, proxy_config=None)?
class Scanner:
    """Helper to get tags and other relevant info from URIs.

    :param timeout: timeout for scanning a URI in ms
    :param proxy_config: dictionary containing proxy config strings.
    """

    def __init__ (
        self,
        timeout: int = 1000,
        proxy_config: dict[str, Any] | None = None,
    ) -> None:
        self._timeout_ms = int(timeout)
        self._proxy_config = proxy_config or {}

    def scan(
        self,
        uri: str,
        timeout: float | None = None,
    ) -> _Result:
        """Scan the given uri collecting relevant metadata.

        :param uri: URI of the resource to scan.
        :type uri: string
        :param timeout: timeout for scanning a URI in ms. Defaults to the
            ``timeout`` value used when creating the scanner.
        :type timeout: int
        :return: A named tuple containing
            ``(uri, tags, duration, seekable, mime)``.
            ``tags`` is a dictionary of lists for all the tags we found.
            ``duration`` is the length of the URI in milliseconds, or
            :class:`None` if the URI has no duration. ``seekable`` is boolean.
            indicating if a seek would succeed.
        """
        timeout = int(timeout or self._timeout_ms)
        pipeline, signals = _setup_pipeline(uri, self._proxy_config)

        try:
            _start_pipeline(pipeline)
            tags, mime, have_audio, duration = _process(pipeline, timeout)
            seekable = _query_seekable(pipeline)
            return _Result(uri, tags, duration, seekable, mime, have_audio)
        finally:
            signals.clear()
            pipeline.set_state(Gst.State.NULL)
            del pipeline

# Turns out it's _much_ faster to just create a new pipeline for every as
# decodebins and other elements don't seem to take well to being reused.
def _setup_pipeline(uri: str, proxy_config=None) -> tuple[Gst.Pipeline, utils.Signals]:
    src = Gst.Element.make_from_uri(Gst.URIType.SRC, uri)
    if not src:
        msg = f"GStreamer can not open: {uri}"
        raise exceptions.ScannerError(msg)

    if proxy_config:
        utils.setup_proxy(src, proxy_config)

    signals = utils.Signals()

    pipeline = Gst.ElementFactory.make("pipeline")
    if pipeline is None:
        msg = "Failed to create GStreamer pipeline element."
        raise exceptions.AudioException(msg)
    pipeline = cast(Gst.Pipeline, pipeline)
    pipeline.add(src)

    if _has_src_pads(src):
        _setup_decodebin(src, src.get_static_pad("src"), pipeline, signals)
    elif _has_dynamic_src_pad(src):
        signals.connect(src, "pad-added", _setup_decodebin, pipeline, signals)
    else:
        msg = "No pads found in source element."
        raise exceptions.ScannerError(msg)

    return pipeline, signals

def _has_src_pads(element) -> bool:
    pads = []
    element.iterate_src_pads().foreach(pads.append)
    return bool(pads)

def _has_dynamic_src_pad(element) -> bool:
    for template in element.get_pad_template_list():
        if (
            template.direction == Gst.PadDirection.SRC
            and template.presence == Gst.PadPresence.SOMETIMES
        ):
            return True
    return False

def _setup_decodebin(element, pad, pipeline, signals) -> None: # noqa: ARG001
    typefind = Gst.ElementFactory.make("typefind")
    if typefind is None:
        msg = "Failed to create GStreamer typefind element."
        raise exceptions.AudioException(msg)

    decodebin = Gst.ElementFactory.make("decodebin")
    if decodebin is None:
        msg = "Failed to create GStreamer decodebin element."
        raise exceptions.AudioException(msg)

    for el in (typefind, decodebin):
        pipeline.add(el)
        el.sync_state_with_parent()

    pad.link(typefind.get_static_pad("sink"))
    typefind.link(decodebin)

    signals.connect(typefind, "have-type", _have_type, decodebin)
    signals.connect(decodebin, "pad-added", _pad_added, pipeline)
    signals.connect(decodebin, "autoplug-select", _autoplug_select)

def _have_type(
    element: Gst.Element,
    _probability: int,
    caps: Gst.Caps,
    decodebin: Gst.Bin,
) -> None:
    decodebin.set_property("sink-caps", caps)
    struct = Gst.Structure.new_empty("have-type")
    struct.set_value("caps", caps.get_structure(0))

    element_bus = element.get_bus()
    if element_bus is None:
        msg = "Failed to get bus of GStreamer element."
        raise exceptions.AudioException(msg)

    message = Gst.Message.new_application(element, struct)
    if message is None:
        msg = "Failed to create GStreamer message."
        raise exceptions.AudioException(msg)

    element_bus.post(message)

def _pad_added(
    element: Gst.Element,
    pad: Gst.Pad,
    pipeline: Gst.Pipeline,
) -> None:
    fakesink = Gst.ElementFactory.make("fakesink")
    if fakesink is None:
        msg = "Failed to create GStreamer fakesink element."
        raise exceptions.AudioException(msg)

    fakesink.set_property("sync", False)

    pipeline.add(fakesink)
    fakesink.sync_state_with_parent()
    fakesink_sink = fakesink.get_static_pad("sink")
    if fakesink_sink is None:
        msg = "Failed to get sink pad of GStreamer fakesink."
        raise exceptions.AudioException(msg)
    pad.link(fakesink_sink)

    raw_caps = Gst.Caps.from_string("audio/x-raw")
    assert raw_caps

    if pad.query_caps().is_subset(raw_caps):
        # Probably won't happen due to autoplug-select fix, but lets play it
        # safe until we've tested more.
        struct = Gst.Structure.new_empty("have-audio")

        element_bus = element.get_bus()
        if element_bus is None:
            msg = "Failed to get bus of GStreamer element."
            raise exceptions.AudioException(msg)

        message = Gst.Message.new_application(element, struct)
        if message is None:
            msg = "Failed to create GStreamer message."
            raise exceptions.AudioException(msg)

        element_bus.post(message)

def _autoplug_select(
    element: Gst.Element,
    _pad: Gst.Pad,
    _caps: Gst.Caps,
    factory: Gst.ElementFactory,
) -> GstAutoplugSelectResult:
    if factory.list_is_type(
        GstElementFactoryListType.DECODER | GstElementFactoryListType.AUDIO,
    ):
        struct = Gst.Structure.new_empty("have-audio")

        element_bus = element.get_bus()
        if element_bus is None:
            msg = "Failed to get bus of GStreamer element."
            raise exceptions.AudioException(msg)

        message = Gst.Message.new_application(element, struct)
        if message is None:
            msg = "Failed to create GStreamer message."
            raise exceptions.AudioException(msg)

        element_bus.post(message)

    if not factory.list_is_type(
        GstElementFactoryListType.DEMUXER
        | GstElementFactoryListType.DEPAYLOADER
        | GstElementFactoryListType.PARSER,
    ):
        return GstAutoplugSelectResult.EXPOSE
    return GstAutoplugSelectResult.TRY

def _start_pipeline(pipeline: Gst.Pipeline) -> None:
    result = pipeline.set_state(Gst.State.PAUSED)
    if result == Gst.StateChangeReturn.NO_PREROLL:
        pipeline.set_state(Gst.State.PLAYING)

def _query_duration(pipeline: Gst.Pipeline) -> tuple[bool, int | None]:
    success, duration = pipeline.query_duration(Gst.Format.TIME)
    if not success:
        duration = None # Make sure error case preserves None.
    elif duration < 0:
        duration = None # Stream without duration.
    else:
        duration = int(duration // Gst.MSECOND)
    return success, duration

def _query_seekable(pipeline: Gst.Pipeline) -> bool:
    query = Gst.Query.new_seeking(Gst.Format.TIME)
    pipeline.query(query)
    return query.parse_seeking()[1]

def _process( # noqa: C901, PLR0911, PLR0912, PLR0915
    pipeline: Gst.Pipeline,
    timeout_ms: int,
) -> tuple[dict[str, Any], str | None, bool, int | None]:
    bus = pipeline.get_bus()
    tags = {}
    mime: str | None = None
    have_audio = False
    missing_message = None
    duration = None

    types = (
        Gst.MessageType.ELEMENT
        | Gst.MessageType.APPLICATION
        | Gst.MessageType.ERROR
        | Gst.MessageType.EOS
        | Gst.MessageType.ASYNC_DONE
        | Gst.MessageType.DURATION_CHANGED
        | Gst.MessageType.TAG
    )

    timeout = timeout_ms
    start = int(time.time() * 1000)
    while timeout > 0:
        msg = bus.timed_pop_filtered(timeout * Gst.MSECOND, types)
        if msg is None:
            break

        structure = msg.get_structure()

        if logger.isEnabledFor(log.TRACE_LOG_LEVEL) and structure:
            debug_text = structure.to_string()
            if len(debug_text) > 77:
                debug_text = debug_text[:77] + "..."
            _trace("element %s: %s", msg.src.get_name(), debug_text)

        if msg.type == Gst.MessageType.ELEMENT:
            if GstPbutils.is_missing_plugin_message(msg):
                missing_message = msg
        elif msg.type == Gst.MessageType.APPLICATION:
            if structure and structure.get_name() == "have-type":
                caps = cast(Gst.Caps | None, structure.get_value("caps"))
                if caps:
                    mime = cast(
                        str,
                        caps.get_name(), # pyright: ignore[reportAttributeAccessIssue]
                    )
                    if mime.startswith("text/") or mime == "application/xml":
                        return tags, mime, have_audio, duration
            elif structure and structure.get_name() == "have-audio":
                have_audio = True
        elif msg.type == Gst.MessageType.ERROR:
            error, _debug = msg.parse_error()
            if (
                missing_message
                and not mime
                and (
                    (structure := missing_message.get_structure())
                    and (caps := structure.get_value("detail"))
                )
            ):
                # gstreamer 1.25.0 to 1.26.2 (inclusive) broke the accessing
                # `caps.get_structure(0).get_name()`, but allow wrapping the
                # object in a context manager. with gstreamer 1.24.x one can
                # not use the structure as a context manager at all. version
                # 1.26.3 will supposedly revert it to the previous behaviour.
                struct = caps.get_structure(0)
                try:
                    mime = struct.get_name()
                except AttributeError:
                    with struct as _struct:
                        mime = _struct.get_name()
                if mime:
                    return tags, mime, have_audio, duration
            raise exceptions.ScannerError(str(error))
        elif msg.type == Gst.MessageType.EOS:
            return tags, mime, have_audio, duration
        elif msg.type == Gst.MessageType.ASYNC_DONE:
            success, duration = _query_duration(pipeline)
            if tags and success:
                return tags, mime, have_audio, duration

            # Don't try workaround for non-seekable sources such as mmssrc:
            if not _query_seekable(pipeline):
                return tags, mime, have_audio, duration

            # Workaround for upstream bug which causes tags/duration to arrive
            # after pre-roll. We get around this by starting to play the track
            # and then waiting for a duration change.
            # https://bugzilla.gnome.org/show_bug.cgi?id=763553
            logger.debug("Using workaround for duration missing before play.")
            result = pipeline.set_state(Gst.State.PLAYING)
            if result == Gst.StateChangeReturn.FAILURE:
                return tags, mime, have_audio, duration

        elif msg.type == Gst.MessageType.DURATION_CHANGED and tags:
            # VBR formats sometimes seem to not have a duration by the time we
            # go back to paused. So just try to get it right away.
            success, duration = _query_duration(pipeline)
            pipeline.set_state(Gst.State.PAUSED)
            if success:
                return tags, mime, have_audio, duration
        elif msg.type == Gst.MessageType.TAG:
            taglist = msg.parse_tag()
            # Note that this will only keep the last tag.
            tags.update(tags_lib.convert_taglist(taglist))

        timeout = timeout_ms - (int(time.time() * 1000) - start)

    msg = f"Timeout after {timeout_ms:d}ms"
    raise exceptions.ScannerError(msg)

if __name__ == " __main__":
    import sys

    from mopidy.internal import path

    logging.basicConfig(
        format="%(asctime)-15s %(levelname)s %(message)s",
        level=log.TRACE_LOG_LEVEL,
    )

    scanner = Scanner(5000)
    for uri in sys.argv[1:]:
        if not Gst.uri_is_valid(uri):
            uri = path.path_to_uri(Path(uri).resolve())
        try:
            result = scanner.scan(uri)
            for key in ("uri", "mime", "duration", "playable", "seekable"):
                value = getattr(result, key)
                print(f"{key:<20} {value}") # noqa: T201
            print("tags") # noqa: T201
            for tag, value in result.tags.items():
                line = f"{tag:<20} {value}"
                if len(line) > 77:
                    line = line[:77] + "..."
                print(line) # noqa: T201
        except exceptions.ScannerError as error:
            print(f"{uri}: {error}") # noqa: T201

```

---

<div class="post-metadata">

**Author:** ![David](https://avatars.discourse-cdn.com/v4/letter/d/aeb1de/32.png) [@David](https://discourse.mopidy.com/u/David)\
**Post date:** [December 16, 2025, 6:44pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/8 "2025-12-16T18:44:07Z")

</div>

Sorry, found it

[https://github.com/mopidy/mopidy/pull/2200/files](https://github.com/mopidy/mopidy/pull/2200/files)

![grafik](https://canada1.discourse-cdn.com/flex036/uploads/mopidy/original/2X/2/2d21762b7ea06a2440ba71977822c171e6e58bc3.png)

You can copy the file from there (push the two squares after scan.py)

---

<div class="post-metadata">

**Author:** ![fmarzocca](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.mopidy.com/fmarzocca/32/1095_2.png) [@fmarzocca](https://discourse.mopidy.com/u/fmarzocca)\
**Post date:** [December 16, 2025, 7:04pm UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/9 "2025-12-16T19:04:45Z")

</div>

Great! Thank you very much!

---

<div class="post-metadata">

**Author:** ![system](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.mopidy.com/system/32/1_2.png) [@system](https://discourse.mopidy.com/u/system)\
**Post date:** [June 17, 2026, 7:05am UTC](https://discourse.mopidy.com/t/after-update-on-debian-trixie-mopidy-local-has-an-error/6139/10 "2026-06-17T07:05:01Z")

</div>

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.
