3 Commits 9f9a9c3d3e ... bff413b86c

Author SHA1 Message Date
  Fabian Peter Hammerle bff413b86c symuid-sync: log when adding play count tag 1 month ago
  Fabian Peter Hammerle b81db9acb2 _tag_interface.Ogg.set_free_int: added missing return value 1 month ago
  Fabian Peter Hammerle da9671ffee symuid.sync: added optional param `play_count_added_cb` 1 month ago
4 changed files with 78 additions and 16 deletions
  1. 6 0
      symuid/__init__.py
  2. 2 1
      symuid/_tag_interface.py
  3. 16 15
      symuid/sync.py
  4. 54 0
      tests/test_sync.py

+ 6 - 0
symuid/__init__.py

@@ -55,6 +55,12 @@ class Track:
             return _tag_interface.Ogg(mutagen_file)
         raise NotImplementedError((track_path, type(mutagen_file)))
 
+    def __eq__(self, other: 'Track') -> bool:
+        return self.path == other.path
+
+    def __hash__(self) -> int:
+        return hash(self.path)
+
     @property
     def path(self):
         return self._iface.track_path

+ 2 - 1
symuid/_tag_interface.py

@@ -264,5 +264,6 @@ class Ogg(_MutagenTagInterface):
                     raise ValueError((self.track_path, tag_key, tag_value))
                 yield (tag_key, int(tag_value[0]))
 
-    def set_free_int(self, tag_label: str, data: int) -> None:
+    def set_free_int(self, tag_label: str, data: int) -> typing.Tuple:
         self._mutagen_file[tag_label] = str(data)
+        return (tag_label, self._mutagen_file[tag_label])

+ 16 - 15
symuid/sync.py

@@ -8,7 +8,9 @@ import symuid
 from symuid._uuid import generate_uuid4_bytes
 
 
-def _log_path(track_path, msg, stream=sys.stdout):
+def _log_path(track_path, msg, stream=None):
+    if not stream: # pytest capsys
+        stream = sys.stdout
     stream.write("{!r}: {}\n".format(track_path, msg))
 
 
@@ -26,18 +28,19 @@ class _SyncPosition:
         self._tracks.add(track)
         self._play_counts.update(track.get_play_counts())
 
-    @property
-    def play_counts(self) -> typing.Set[symuid.PlayCount]:
-        return self._play_counts
-
-    @property
-    def tracks(self) -> typing.Set[symuid.Track]:
-        return self._tracks
+    def sync(self, play_count_added_cb=None) -> None:
+        for track in self._tracks:
+            track_play_counts = set(track.get_play_counts())
+            for play_count in self._play_counts:
+                if play_count not in track_play_counts:
+                    track.register_play_count(
+                        play_count, tag_set_cb=play_count_added_cb)
 
     def __repr__(self) -> str:
         return repr(vars(self))
 
-def sync(path, path_ignore_regex, show_ignored=False):
+def sync(path, path_ignore_regex, show_ignored=False,
+         play_count_added_cb=None):
     sync_positions = collections.defaultdict(_SyncPosition)
     for track in symuid.Track.walk(
             root_path=path,
@@ -52,11 +55,7 @@ def sync(path, path_ignore_regex, show_ignored=False):
                 track.get_uuid()))
         sync_positions[track.get_uuid()].add_track(track)
     for sync_position in sync_positions.values():
-        for track in sync_position.tracks:
-            track_play_counts = set(track.get_play_counts())
-            for play_count in sync_position.play_counts:
-                if play_count not in track_play_counts:
-                    track.register_play_count(play_count)
+        sync_position.sync(play_count_added_cb=play_count_added_cb)
 
 
 def _init_argparser():
@@ -81,4 +80,6 @@ def _init_argparser():
 def _main():
     argparser = _init_argparser()
     args = argparser.parse_args()
-    sync(**vars(args))
+    sync(**vars(args),
+         play_count_added_cb=lambda track, tag:
+         _log_path(track.path, 'added play count tag {!r}'.format(tag)))

+ 54 - 0
tests/test_sync.py

@@ -106,3 +106,57 @@ def test_sync_play_count(tmpdir, tracks_dir_path):
     assert len(play_counts_b) == 1
     assert {pc.register_dt.timestamp() for pc in play_counts_b} \
         == pytest.approx({3})
+
+
+def test_sync_play_count_callback(tmpdir, tracks_dir_path):
+    shutil.copyfile(
+        src=os.path.join(tracks_dir_path, 'id3v2.4-empty.mp3'),
+        dst=tmpdir.join('a1.mp3'),
+    )
+    shutil.copyfile(
+        src=os.path.join(tracks_dir_path, 'ogg-vorbis-empty.ogg'),
+        dst=tmpdir.join('a2.ogg'),
+    )
+    uuid_a = generate_uuid4_bytes()
+    track_a1 = Track(tmpdir.join('a1.mp3'))
+    track_a1.assign_uuid(uuid_a)
+    track_a2 = Track(tmpdir.join('a2.ogg'))
+    track_a2.assign_uuid(uuid_a)
+    track_a1.register_play_count(PlayCount(
+        player='cmus',
+        library_id='lib1',
+        register_dt=unix_epoch_time_to_datetime_utc(0),
+        count=21,
+    ))
+    play_count_added_cb = unittest.mock.MagicMock()
+    sync(tmpdir, path_ignore_regex=DUMMY_PATH_IGNORE_REGEX,
+         play_count_added_cb=play_count_added_cb)
+    play_count_added_cb.assert_called_once_with(
+        track_a2, ('symuid:pcnt:cmus:lib1:0', ['21']))
+
+
+def test_sync_play_count_callback_main(capsys, tmpdir, tracks_dir_path):
+    shutil.copyfile(
+        src=os.path.join(tracks_dir_path, 'id3v2.4-empty.mp3'),
+        dst=tmpdir.join('a1.mp3'),
+    )
+    shutil.copyfile(
+        src=os.path.join(tracks_dir_path, 'ogg-vorbis-empty.ogg'),
+        dst=tmpdir.join('a2.ogg'),
+    )
+    uuid_a = generate_uuid4_bytes()
+    track_a1 = Track(tmpdir.join('a1.mp3'))
+    track_a1.assign_uuid(uuid_a)
+    track_a2 = Track(tmpdir.join('a2.ogg'))
+    track_a2.assign_uuid(uuid_a)
+    track_a1.register_play_count(PlayCount(
+        player='cmus',
+        library_id='lib1',
+        register_dt=unix_epoch_time_to_datetime_utc(0),
+        count=21,
+    ))
+    with unittest.mock.patch('sys.argv', ['', tmpdir.strpath]):
+        _main()
+    out, _ = capsys.readouterr()
+    assert out == "{!r}: added play count tag ('symuid:pcnt:cmus:lib1:0', ['21'])\n" \
+        .format(track_a2.path)