Fix parsing tags from smb provider (#530)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Tue, 14 Mar 2023 19:29:23 +0000 (20:29 +0100)
committerGitHub <noreply@github.com>
Tue, 14 Mar 2023 19:29:23 +0000 (20:29 +0100)
Fix some issues with parsing tags (e.g. duration) from the SMB music
provider and set some other defaults

.github/workflows/test.yml
music_assistant/server/helpers/compare.py
music_assistant/server/helpers/tags.py
music_assistant/server/providers/filesystem_local/base.py
music_assistant/server/providers/filesystem_smb/manifest.json

index 97f5364c1f8131dc1f0846cfdd51d4b572b5df9b..41123d4965c050d0ef242793f06366fcb2201b86 100644 (file)
@@ -24,7 +24,7 @@ jobs:
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip build setuptools
-          pip install .[test]
+          pip install . .[test]
       - name: Lint/test with pre-commit
         run: pre-commit run --all-files
 
@@ -47,6 +47,6 @@ jobs:
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip build setuptools
-          pip install .[test]
+          pip install . .[test]
       - name: Pytest
         run: pytest --durations 10 --cov-report term-missing --cov=music_assistant --cov-report=xml tests/
index 0c8fe79e25f6ddb2ae4f9895787e166057c2610f..114eae97f99edbceb4714a141902fab4eceb2bda 100644 (file)
@@ -2,7 +2,6 @@
 from __future__ import annotations
 
 from music_assistant.common.helpers.util import create_safe_string, create_sort_name
-from music_assistant.common.models.enums import AlbumType
 from music_assistant.common.models.media_items import (
     Album,
     Artist,
@@ -238,11 +237,9 @@ def compare_track(left_track: Track, right_track: Track):
             for right_album in right_track.albums:
                 if compare_album(left_album, right_album):
                     return True
-    # fallback: both albums are compilations and (near-exact) track duration match
-    if (
-        abs(left_track.duration - right_track.duration) <= 2
-        and left_track.album.album_type in (AlbumType.UNKNOWN, AlbumType.COMPILATION)
-        and right_track.album.album_type in (AlbumType.UNKNOWN, AlbumType.COMPILATION)
+    # fallback: exact album match and (near-exact) track duration match
+    if abs(left_track.duration - right_track.duration) <= 3 and compare_album(
+        left_track.album, right_track.album
     ):
         return True
     return False
index 0081a7dd8dcbbdb7e44771cddf919dc2a99ec871..fd064043fc78371002fc4e2e893247355fc34143 100644 (file)
@@ -199,7 +199,9 @@ class AudioTags:
         return self.tags.get(key, default)
 
 
-async def parse_tags(input_file: str | AsyncGenerator[bytes, None]) -> AudioTags:
+async def parse_tags(
+    input_file: str | AsyncGenerator[bytes, None], file_size: int | None = None
+) -> AudioTags:
     """Parse tags from a media file.
 
     input_file may be a (local) filename/url accessible by ffmpeg or
@@ -227,12 +229,17 @@ async def parse_tags(input_file: str | AsyncGenerator[bytes, None]) -> AudioTags
         if file_path == "-":
             # feed the file contents to the process
             async def chunk_feeder():
-                # pylint: disable=protected-access
+                bytes_written = 0
                 async for chunk in input_file:
                     try:
                         await proc.write(chunk)
                     except BrokenPipeError:
                         break  # race-condition: read enough data for tags
+
+                    # grabbing the first 5MB is enough to get the embedded tags
+                    bytes_written += len(chunk)
+                    if bytes_written > (5 * 1024000):
+                        break
                 proc.write_eof()
 
             proc.attach_task(chunk_feeder())
@@ -242,7 +249,11 @@ async def parse_tags(input_file: str | AsyncGenerator[bytes, None]) -> AudioTags
             data = json.loads(res)
             if error := data.get("error"):
                 raise InvalidDataError(error["string"])
-            return AudioTags.parse(data)
+            tags = AudioTags.parse(data)
+            if not tags.duration and file_size and tags.bit_rate:
+                # estimate duration from filesize/bitrate
+                tags.duration = int((file_size * 8) / tags.bit_rate)
+            return tags
         except (KeyError, ValueError, JSONDecodeError, InvalidDataError) as err:
             raise InvalidDataError(f"Unable to retrieve info for {file_path}: {str(err)}") from err
 
index 6c8f85e6f538b4535fffde4bc38777fb6a888e42..9814cb19f3ce1538a53869b8345689a82ce68893 100644 (file)
@@ -343,7 +343,7 @@ class FileSystemProviderBase(MusicProvider):
 
         # parse tags
         input_file = file_item.local_path or self.read_file_content(file_item.absolute_path)
-        tags = await parse_tags(input_file)
+        tags = await parse_tags(input_file, file_item.file_size)
 
         name, version = parse_title_and_version(tags.title)
         track = Track(
index 391a8ff2895746824b08a33201a7337fabadc0ed..ae7844d89309d0590bffeae1018211ba515bd19e 100644 (file)
@@ -65,7 +65,7 @@
       "key": "is_direct_tcp",
       "type": "boolean",
       "label": "Use Direct TCP",
-      "default_value": false,
+      "default_value": true,
       "description": "Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication. The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).",
       "advanced": true,
       "required": false