pyrekordbox 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. docs/source/formats/anlz.md +178 -7
  2. docs/source/formats/db6.md +1 -1
  3. docs/source/index.md +2 -6
  4. docs/source/quickstart.md +68 -45
  5. docs/source/tutorial/index.md +1 -1
  6. pyrekordbox/__init__.py +1 -1
  7. pyrekordbox/_version.py +2 -2
  8. pyrekordbox/anlz/file.py +39 -0
  9. pyrekordbox/anlz/structs.py +3 -5
  10. pyrekordbox/config.py +71 -27
  11. pyrekordbox/db6/database.py +260 -33
  12. pyrekordbox/db6/registry.py +22 -0
  13. pyrekordbox/db6/tables.py +3 -4
  14. {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/METADATA +12 -11
  15. pyrekordbox-0.2.2.dist-info/RECORD +80 -0
  16. {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/top_level.txt +0 -2
  17. tests/test_config.py +175 -0
  18. tests/test_db6.py +78 -0
  19. build/lib/build/lib/docs/source/conf.py +0 -178
  20. build/lib/build/lib/pyrekordbox/__init__.py +0 -22
  21. build/lib/build/lib/pyrekordbox/__main__.py +0 -204
  22. build/lib/build/lib/pyrekordbox/_version.py +0 -16
  23. build/lib/build/lib/pyrekordbox/anlz/__init__.py +0 -127
  24. build/lib/build/lib/pyrekordbox/anlz/file.py +0 -186
  25. build/lib/build/lib/pyrekordbox/anlz/structs.py +0 -299
  26. build/lib/build/lib/pyrekordbox/anlz/tags.py +0 -508
  27. build/lib/build/lib/pyrekordbox/config.py +0 -596
  28. build/lib/build/lib/pyrekordbox/db6/__init__.py +0 -45
  29. build/lib/build/lib/pyrekordbox/db6/aux_files.py +0 -213
  30. build/lib/build/lib/pyrekordbox/db6/database.py +0 -1808
  31. build/lib/build/lib/pyrekordbox/db6/registry.py +0 -304
  32. build/lib/build/lib/pyrekordbox/db6/tables.py +0 -1618
  33. build/lib/build/lib/pyrekordbox/logger.py +0 -23
  34. build/lib/build/lib/pyrekordbox/mysettings/__init__.py +0 -32
  35. build/lib/build/lib/pyrekordbox/mysettings/file.py +0 -369
  36. build/lib/build/lib/pyrekordbox/mysettings/structs.py +0 -282
  37. build/lib/build/lib/pyrekordbox/utils.py +0 -162
  38. build/lib/build/lib/pyrekordbox/xml.py +0 -1294
  39. build/lib/build/lib/tests/__init__.py +0 -3
  40. build/lib/build/lib/tests/test_anlz.py +0 -206
  41. build/lib/build/lib/tests/test_db6.py +0 -1039
  42. build/lib/build/lib/tests/test_mysetting.py +0 -203
  43. build/lib/build/lib/tests/test_xml.py +0 -629
  44. build/lib/docs/source/conf.py +0 -178
  45. build/lib/pyrekordbox/__init__.py +0 -22
  46. build/lib/pyrekordbox/__main__.py +0 -204
  47. build/lib/pyrekordbox/_version.py +0 -16
  48. build/lib/pyrekordbox/anlz/__init__.py +0 -127
  49. build/lib/pyrekordbox/anlz/file.py +0 -186
  50. build/lib/pyrekordbox/anlz/structs.py +0 -299
  51. build/lib/pyrekordbox/anlz/tags.py +0 -508
  52. build/lib/pyrekordbox/config.py +0 -596
  53. build/lib/pyrekordbox/db6/__init__.py +0 -45
  54. build/lib/pyrekordbox/db6/aux_files.py +0 -213
  55. build/lib/pyrekordbox/db6/database.py +0 -1808
  56. build/lib/pyrekordbox/db6/registry.py +0 -304
  57. build/lib/pyrekordbox/db6/tables.py +0 -1618
  58. build/lib/pyrekordbox/logger.py +0 -23
  59. build/lib/pyrekordbox/mysettings/__init__.py +0 -32
  60. build/lib/pyrekordbox/mysettings/file.py +0 -369
  61. build/lib/pyrekordbox/mysettings/structs.py +0 -282
  62. build/lib/pyrekordbox/utils.py +0 -162
  63. build/lib/pyrekordbox/xml.py +0 -1294
  64. build/lib/tests/__init__.py +0 -3
  65. build/lib/tests/test_anlz.py +0 -206
  66. build/lib/tests/test_db6.py +0 -1039
  67. build/lib/tests/test_mysetting.py +0 -203
  68. build/lib/tests/test_xml.py +0 -629
  69. pyrekordbox-0.2.1.dist-info/RECORD +0 -129
  70. {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/LICENSE +0 -0
  71. {pyrekordbox-0.2.1.dist-info → pyrekordbox-0.2.2.dist-info}/WHEEL +0 -0
@@ -12,7 +12,7 @@ from sqlalchemy import create_engine, or_, event, MetaData
12
12
  from sqlalchemy.orm import Session
13
13
  from sqlalchemy.exc import NoResultFound
14
14
  from sqlalchemy.sql.sqltypes import DateTime, String
15
- from packaging import version
15
+ import packaging.version
16
16
  from ..utils import get_rekordbox_pid
17
17
  from ..config import get_config
18
18
  from ..anlz import get_anlz_paths, read_anlz_files
@@ -26,7 +26,7 @@ try:
26
26
  except ImportError:
27
27
  import sqlite3
28
28
 
29
- MAX_VERSION = version.parse("6.6.5")
29
+ MAX_VERSION = packaging.version.parse("6.6.5")
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
@@ -109,7 +109,7 @@ def open_rekordbox_database(path=None, key="", unlock=True, sql_driver=None):
109
109
 
110
110
  if unlock:
111
111
  if not key:
112
- ver = version.parse(rb6_config["version"])
112
+ ver = packaging.version.parse(rb6_config["version"])
113
113
  if ver >= MAX_VERSION:
114
114
  raise IncompatibleVersionError(rb6_config["version"])
115
115
  try:
@@ -207,15 +207,15 @@ class Rekordbox6Database:
207
207
  raise FileNotFoundError(f"File '{path}' does not exist!")
208
208
  # Open database
209
209
  if unlock:
210
+ if "dp" in rb6_config:
211
+ key = rb6_config["dp"]
210
212
  if not key:
211
- ver = version.parse(rb6_config["version"])
213
+ ver = packaging.version.parse(rb6_config["version"])
212
214
  if ver >= MAX_VERSION:
213
215
  raise IncompatibleVersionError(rb6_config["version"])
214
- try:
215
- key = rb6_config["dp"]
216
- except KeyError:
216
+ else:
217
217
  raise ValueError("Could not unlock database: No key found")
218
- logger.info("Key: %s", key)
218
+ logger.info("Key: %s", key)
219
219
  # Unlock database and create engine
220
220
  url = f"sqlite+pysqlcipher://:{key}@/{path}?"
221
221
  engine = create_engine(url, module=sqlite3)
@@ -925,12 +925,12 @@ class Rekordbox6Database:
925
925
  .order_by(tables.DjmdSongPlaylist.TrackNo)
926
926
  )
927
927
  moved = list()
928
- self.registry.disable_tracking()
929
- for song in query:
930
- song.TrackNo -= 1
931
- song.updated_at = now
932
- moved.append(song)
933
- self.registry.enable_tracking()
928
+ with self.registry.disabled():
929
+ for song in query:
930
+ song.TrackNo -= 1
931
+ song.updated_at = now
932
+ moved.append(song)
933
+
934
934
  if moved:
935
935
  self.registry.on_move(moved)
936
936
 
@@ -1092,9 +1092,8 @@ class Rekordbox6Database:
1092
1092
  )
1093
1093
  for pl in query:
1094
1094
  pl.Seq += 1
1095
- self.registry.disable_tracking()
1096
- pl.updated_at = now
1097
- self.registry.enable_tracking()
1095
+ with self.registry.disabled():
1096
+ pl.updated_at = now
1098
1097
 
1099
1098
  # Add new playlist to database
1100
1099
  # First create with name 'New playlist'
@@ -1390,10 +1389,9 @@ class Rekordbox6Database:
1390
1389
  # Set seq number and update time *before* other playlists to ensure
1391
1390
  # right USN increment order
1392
1391
  playlist.ParentID = parent_id
1393
- self.registry.disable_tracking()
1394
- playlist.Seq = seq
1395
- playlist.updated_at = now
1396
- self.registry.enable_tracking()
1392
+ with self.registry.disabled():
1393
+ playlist.Seq = seq
1394
+ playlist.updated_at = now
1397
1395
 
1398
1396
  if not insert_at_end:
1399
1397
  # Update seq numbers higher than the new seq number in *new* parent
@@ -1402,9 +1400,8 @@ class Rekordbox6Database:
1402
1400
  # Update time of other playlists are left unchanged
1403
1401
  pl.Seq += 1
1404
1402
  # Each move counts as one USN increment, so disable for update time
1405
- self.registry.disable_tracking()
1406
- pl.updated_at = now
1407
- self.registry.enable_tracking()
1403
+ with self.registry.disabled():
1404
+ pl.updated_at = now
1408
1405
 
1409
1406
  # Update seq numbers higher than the old seq number in *old* parent
1410
1407
  # USN is not updated here
@@ -1463,17 +1460,15 @@ class Rekordbox6Database:
1463
1460
  # right USN increment order
1464
1461
  playlist.Seq = seq
1465
1462
  # Each move counts as one USN increment, so disable for update time
1466
- self.registry.disable_tracking()
1467
- playlist.updated_at = now
1468
- self.registry.enable_tracking()
1463
+ with self.registry.disabled():
1464
+ playlist.updated_at = now
1469
1465
 
1470
1466
  # Set seq number and update time for playlists between old_seq and seq
1471
1467
  for pl in other_playlists:
1472
1468
  pl.Seq += delta_seq
1473
1469
  # Each move counts as one USN increment, so disable for update time
1474
- self.registry.disable_tracking()
1475
- pl.updated_at = now
1476
- self.registry.enable_tracking()
1470
+ with self.registry.disabled():
1471
+ pl.updated_at = now
1477
1472
 
1478
1473
  def rename_playlist(self, playlist, name):
1479
1474
  """Renames a playlist or playlist folder.
@@ -1509,9 +1504,241 @@ class Rekordbox6Database:
1509
1504
  # Update name of playlist
1510
1505
  playlist.Name = name
1511
1506
  # Update update time: USN not incremented
1512
- self.registry.disable_tracking()
1513
- playlist.updated_at = now
1514
- self.registry.enable_tracking()
1507
+ with self.registry.disabled():
1508
+ playlist.updated_at = now
1509
+
1510
+ def add_album(
1511
+ self, name, artist=None, image_path=None, compilation=None, search_str=None
1512
+ ):
1513
+ """Adds a new album to the database.
1514
+
1515
+ Parameters
1516
+ ----------
1517
+ name : str
1518
+ The name of the album. Must be a unique name (case-sensitive).
1519
+ If an album with the same name already exists in the database,
1520
+ use the `ID` of the existing album instead.
1521
+ artist : str or int or DjmdArtist, optional
1522
+ The artist of the album. Can either be a :class:`DjmdArtist` object
1523
+ or an artist ID.
1524
+ image_path : str, optional
1525
+ The path to the album cover image.
1526
+ compilation : bool, optional
1527
+ Whether the album is a compilation album. If not given, the
1528
+ default value of `False` is used.
1529
+ search_str : str, optional
1530
+ The search string of the album.
1531
+
1532
+ Returns
1533
+ -------
1534
+ album : DjmdAlbum
1535
+ The newly created album.
1536
+
1537
+ Raises
1538
+ ------
1539
+ ValueError : If an album with the same name already exists in the database.
1540
+
1541
+ Examples
1542
+ --------
1543
+ Add a new album to the database:
1544
+
1545
+ >>> db = Rekordbox6Database()
1546
+ >>> db.add_album(name="Album 1")
1547
+ <DjmdAlbum(148754249 Name=Album 1)>
1548
+
1549
+ Add a new album to the database with an album artist:
1550
+
1551
+ >>> artist = db.get_artist(Name="Artist 1").one() # noqa
1552
+ >>> db.add_album(name="Album 2", artist=artist)
1553
+ <DjmdAlbum(148754249 Name=Album 2)>
1554
+
1555
+ For setting the album of a track, the usual procedure is to first
1556
+ check if an entry with the same album name already exists in the database,
1557
+ and if not, add a new album:
1558
+
1559
+ >>> name = "Album name"
1560
+ >>> content = db.get_content().one()
1561
+ >>> album = db.get_album(Name=name).one_or_none()
1562
+ >>> if album is None:
1563
+ ... album = db.add_album(name=name)
1564
+ >>> content.AlbumID = album.ID
1565
+ """
1566
+ # Check if album already exists
1567
+ query = self.query(tables.DjmdAlbum).filter_by(Name=name)
1568
+ if query.count() > 0:
1569
+ raise ValueError(f"Album '{name}' already exists in database")
1570
+
1571
+ # Get artist ID
1572
+ if artist is not None:
1573
+ if isinstance(artist, (int, str)):
1574
+ artist = self.get_artist(ID=artist)
1575
+ artist = artist.ID
1576
+
1577
+ album = tables.DjmdAlbum.create(
1578
+ ID=self.generate_unused_id(tables.DjmdAlbum),
1579
+ Name=name,
1580
+ AlbumArtistID=artist,
1581
+ ImagePath=image_path,
1582
+ Compilation=compilation,
1583
+ SearchStr=search_str,
1584
+ )
1585
+ self.add(album)
1586
+ self.flush()
1587
+ return album
1588
+
1589
+ def add_artist(self, name, search_str=None):
1590
+ """Adds a new artist to the database.
1591
+
1592
+ Parameters
1593
+ ----------
1594
+ name : str
1595
+ The name of the artist. Must be a unique name (case-sensitive).
1596
+ If an artist with the same name already exists in the database,
1597
+ use the `ID` of the existing artist instead.
1598
+ search_str : str, optional
1599
+ The search string of the artist.
1600
+
1601
+ Returns
1602
+ -------
1603
+ artist : DjmdArtist
1604
+ The newly created artist.
1605
+
1606
+ Raises
1607
+ ------
1608
+ ValueError : If an artist with the same name already exists in the database.
1609
+
1610
+ Examples
1611
+ --------
1612
+ Add a new artist to the database:
1613
+
1614
+ >>> db = Rekordbox6Database()
1615
+ >>> db.add_artist(name="Artist 1")
1616
+ <DjmdArtist(123456789, Name='Artist 1')>
1617
+
1618
+ Add a new artist to the database with a custom search string:
1619
+
1620
+ >>> db.add_artist(name="Artist 2", search_str="artist 2")
1621
+ <DjmdArtist(123456789, Name='Artist 2')>
1622
+
1623
+ For setting the artist of a track, the usual procedure is to first
1624
+ check if an entry with the same artist name already exists in the database,
1625
+ and if not, add a new artist:
1626
+
1627
+ >>> name = "Artist name"
1628
+ >>> content = db.get_content().one()
1629
+ >>> artist = db.get_artist(Name=name).one_or_none()
1630
+ >>> if artist is None:
1631
+ ... artist = db.add_artist(name=name)
1632
+ >>> content.ArtistID = artist.ID
1633
+ """
1634
+ # Check if artist already exists
1635
+ query = self.query(tables.DjmdArtist).filter_by(Name=name)
1636
+ if query.count() > 0:
1637
+ raise ValueError(f"Artist '{name}' already exists in database")
1638
+
1639
+ id_ = self.generate_unused_id(tables.DjmdArtist)
1640
+ artist = tables.DjmdArtist.create(ID=id_, Name=name, SearchStr=search_str)
1641
+ self.add(artist)
1642
+ self.flush()
1643
+ return artist
1644
+
1645
+ def add_genre(self, name):
1646
+ """Adds a new genre to the database.
1647
+
1648
+ Parameters
1649
+ ----------
1650
+ name : str
1651
+ The name of the genre. Must be a unique name (case-sensitive).
1652
+ If a genre with the same name already exists in the database,
1653
+ use the `ID` of the existing genre instead.
1654
+
1655
+ Returns
1656
+ -------
1657
+ genre : DjmdGenre
1658
+ The newly created genre.
1659
+
1660
+ Raises
1661
+ ------
1662
+ ValueError : If a genre with the same name already exists in the database.
1663
+
1664
+ Examples
1665
+ --------
1666
+ Add a new genre to the database:
1667
+
1668
+ >>> db = Rekordbox6Database()
1669
+ >>> db.add_genre(name="Genre 1")
1670
+ <DjmdGenre(123456789 Name=Genre 1)>
1671
+
1672
+ For setting the genre of a track, the usual procedure is to first
1673
+ check if an entry with the same genre name already exists in the database,
1674
+ and if not, add a new genre:
1675
+
1676
+ >>> name = "Genre name"
1677
+ >>> content = db.get_content().one()
1678
+ >>> genre = db.get_genre(Name=name).one_or_none()
1679
+ >>> if genre is None:
1680
+ ... genre = db.add_genre(name=name)
1681
+ >>> content.GenreID = genre.ID
1682
+ """
1683
+ # Check if genre already exists
1684
+ query = self.query(tables.DjmdGenre).filter_by(Name=name)
1685
+ if query.count() > 0:
1686
+ raise ValueError(f"Genre '{name}' already exists in database")
1687
+
1688
+ id_ = self.generate_unused_id(tables.DjmdGenre)
1689
+ genre = tables.DjmdGenre.create(ID=id_, Name=name)
1690
+ self.add(genre)
1691
+ self.flush()
1692
+ return genre
1693
+
1694
+ def add_label(self, name):
1695
+ """Adds a new label to the database.
1696
+
1697
+ Parameters
1698
+ ----------
1699
+ name : str
1700
+ The name of the label. Must be a unique name (case-sensitive).
1701
+ If a label with the same name already exists in the database,
1702
+ use the `ID` of the existing label instead.
1703
+
1704
+ Returns
1705
+ -------
1706
+ label : DjmdLabel
1707
+ The newly created label.
1708
+
1709
+ Raises
1710
+ ------
1711
+ ValueError : If a label with the same name already exists in the database.
1712
+
1713
+ Examples
1714
+ --------
1715
+ Add a new label to the database:
1716
+
1717
+ >>> db = Rekordbox6Database()
1718
+ >>> db.add_label(name="Label 1")
1719
+ <DjmdLabel(123456789 Name=Label 1)>
1720
+
1721
+ For setting the label of a track, the usual procedure is to first
1722
+ check if an entry with the same label name already exists in the database,
1723
+ and if not, add a new label:
1724
+
1725
+ >>> name = "Label name"
1726
+ >>> content = db.get_content().one()
1727
+ >>> label = db.get_label(Name=name).one_or_none()
1728
+ >>> if label is None:
1729
+ ... label = db.add_label(name=name)
1730
+ >>> content.LabelID = label.ID
1731
+ """
1732
+ # Check if label already exists
1733
+ query = self.query(tables.DjmdLabel).filter_by(Name=name)
1734
+ if query.count() > 0:
1735
+ raise ValueError(f"Label '{name}' already exists in database")
1736
+
1737
+ id_ = self.generate_unused_id(tables.DjmdLabel)
1738
+ label = tables.DjmdLabel.create(ID=id_, Name=name)
1739
+ self.add(label)
1740
+ self.flush()
1741
+ return label
1515
1742
 
1516
1743
  # ----------------------------------------------------------------------------------
1517
1744
 
@@ -3,6 +3,7 @@
3
3
  # Date: 2023-08-07
4
4
 
5
5
  import logging
6
+ from contextlib import contextmanager
6
7
 
7
8
  logger = logging.getLogger(__name__)
8
9
 
@@ -103,6 +104,27 @@ class RekordboxAgentRegistry:
103
104
  """Disables the tracking of database changes."""
104
105
  cls.__enabled__ = False
105
106
 
107
+ @classmethod
108
+ @contextmanager
109
+ def disabled(cls):
110
+ """Context manager to temporarily disable the tracking of database changes.
111
+
112
+ Examples
113
+ --------
114
+ >>> registry = RekordboxAgentRegistry(db) # noqa
115
+ >>> registry.__enabled__
116
+ True
117
+
118
+ >>> with registry.disabled():
119
+ ... print(registry.__enabled__)
120
+ False
121
+ """
122
+ enabled = cls.__enabled__
123
+ cls.disable_tracking()
124
+ yield cls
125
+ if enabled:
126
+ cls.enable_tracking()
127
+
106
128
  def get_registries(self):
107
129
  """Returns all agent registries.
108
130
 
pyrekordbox/db6/tables.py CHANGED
@@ -98,10 +98,9 @@ class Base(DeclarativeBase):
98
98
 
99
99
  @classmethod
100
100
  def create(cls, **kwargs):
101
- RekordboxAgentRegistry.disable_tracking()
102
- # noinspection PyArgumentList
103
- self = cls(**kwargs)
104
- RekordboxAgentRegistry.enable_tracking()
101
+ with RekordboxAgentRegistry.disabled():
102
+ # noinspection PyArgumentList
103
+ self = cls(**kwargs)
105
104
  return self
106
105
 
107
106
  @classmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyrekordbox
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Inofficial Python package for interacting with the library of Pioneers Rekordbox DJ software.
5
5
  Author-email: Dylan Jones <dylanljones94@gmail.com>
6
6
  License: MIT License
@@ -50,19 +50,14 @@ License-File: LICENSE
50
50
  Requires-Dist: bidict >=0.21.0
51
51
  Requires-Dist: blowfish >=0.6.0
52
52
  Requires-Dist: construct >=2.10.0
53
- Requires-Dist: hypothesis >=6.0.0
54
53
  Requires-Dist: numpy >=1.19.0
55
54
  Requires-Dist: packaging
56
- Requires-Dist: pytest >=6.2.0
57
55
  Requires-Dist: psutil >=5.9.0
58
- Requires-Dist: setuptools >=60.0.0
59
- Requires-Dist: setuptools-scm[toml] >=4
60
56
  Requires-Dist: sqlalchemy >=2.0.0
61
- Provides-Extra: build
62
- Requires-Dist: wheel >=0.37.0 ; extra == 'build'
63
57
  Provides-Extra: test
58
+ Requires-Dist: hypothesis >=6.0.0 ; extra == 'test'
59
+ Requires-Dist: pytest >=6.2.0 ; extra == 'test'
64
60
  Requires-Dist: pytest-cov ; extra == 'test'
65
- Requires-Dist: wheel >=0.37.0 ; extra == 'test'
66
61
 
67
62
 
68
63
  <p align="center">
@@ -88,7 +83,7 @@ Pioneers Rekordbox DJ Software. It currently supports
88
83
  - Analysis files (ANLZ)
89
84
  - My-Setting files
90
85
 
91
- Tested Rekordbox versions: ``5.8.6 | 6.5.3``
86
+ Tested Rekordbox versions: ``5.8.6 | 6.5.3 | 6.7.7``
92
87
 
93
88
 
94
89
  |⚠️| This project is still under development and might contain bugs or have breaking API changes in the future. |
@@ -112,7 +107,7 @@ where `VERSION` is a release, tag or branch name.
112
107
 
113
108
  Unlocking the new Rekordbox 6 `master.db` database file requires [SQLCipher][sqlcipher].
114
109
  Pyrekordbox makes no attempt to download/install SQLCipher, as it is a
115
- pure Python package - whereas the SQLCipher/pysqlcipher3 installation is
110
+ pure Python package - whereas the SQLCipher/sqlcipher3 installation is
116
111
  platform-dependent and can not be installed via ``pip``.
117
112
 
118
113
  #### Windows
@@ -205,7 +200,7 @@ So far only a few tables support adding or deleting entries:
205
200
  - ``DjmdSongPlaylist``: Songs in a playlist
206
201
 
207
202
  Starting from Rekordbox version ``6.6.5`` Pioneer obfuscated the ``app.asar`` file
208
- contents, breaking the key extraction (see [this issue](https://github.com/dylanljones/pyrekordbox/issues/64) for more details).
203
+ contents, breaking the key extraction (see [this discussion](https://github.com/dylanljones/pyrekordbox/discussions/97) for more details).
209
204
  If you are using a later version of Rekorbox and have no cached key from a previous
210
205
  version, the database can not be unlocked automatically.
211
206
  The command line interface of ``pyrekordbox`` provides a command for downloading
@@ -304,6 +299,8 @@ sync = mysett.get("sync")
304
299
  quant = mysett.get("quantize")
305
300
  ````
306
301
 
302
+ The `DEVSETTING.DAT` file is still not supported
303
+
307
304
 
308
305
  ## 💡 File formats
309
306
 
@@ -323,6 +320,9 @@ If you encounter an issue or want to contribute to pyrekordbox, please feel free
323
320
  `pyrekordbox` and the commit-message style can be found in
324
321
  [CONTRIBUTING].
325
322
 
323
+ For general questions or discussions about Rekordbox, please use [GitHub Discussions][discussions]
324
+ instead of opening an issue.
325
+
326
326
  Pyrekordbox is tested on Windows and MacOS, however some features can't be tested in
327
327
  the CI setup since it requires a working Rekordbox installation.
328
328
 
@@ -374,6 +374,7 @@ the CI setup since it requires a working Rekordbox installation.
374
374
  [mysettings-doc]: https://pyrekordbox.readthedocs.io/en/stable/formats/mysetting.html
375
375
 
376
376
  [new-issue]: https://github.com/dylanljones/pyrekordbox/issues/new/choose
377
+ [discussions]: https://github.com/dylanljones/pyrekordbox/discussions
377
378
  [CONTRIBUTING]: https://github.com/dylanljones/pyrekordbox/blob/master/CONTRIBUTING.md
378
379
  [CHANGELOG]: https://github.com/dylanljones/pyrekordbox/blob/master/CHANGELOG.md
379
380
  [INSTALLATION]: https://github.com/dylanljones/pyrekordbox/blob/master/INSTALLATION.md
@@ -0,0 +1,80 @@
1
+ docs/Makefile,sha256=4zv3TVkTACm6JBaKgTES3ZI9cETXgM6ULbZkXZP1as8,638
2
+ docs/make.bat,sha256=s8EuuVXNRnn4xmWLWTpk3Z01aqJSJT8ymrmK6ux0zbc,769
3
+ docs/source/api.md,sha256=IKKMeQ2wFSS_ippOWJ63ELtMjfkNe27KjWcFFEdUXnE,276
4
+ docs/source/conf.py,sha256=lPfAJtd0VY7uEDmHrEfHvQzNJg6WN_1seM9pCFc2e6E,6244
5
+ docs/source/index.md,sha256=_73xtwAH2OF0btxHt2Kre4k1cP3N-OhsyDL-Oc7NKCI,2823
6
+ docs/source/installation.md,sha256=RoCHv74LONL5QyBBVKXPOOqRmAN2GUzcvInwnlztM6M,40
7
+ docs/source/quickstart.md,sha256=-v7FE_08KDSUAdbmvrY-7QImKLpR8vA3W6mTFE-uWPs,6299
8
+ docs/source/requirements.txt,sha256=7YYc-tO0BQcBVUa4GvM1r1G5Ma9gkwGZpNbAKlJL97s,117
9
+ docs/source/_static/images/anlz_beat.svg,sha256=cVooi17HFw3mOT39cFNB05tr-cZwbK_Hjmho6eBJNX8,2924
10
+ docs/source/_static/images/anlz_file.svg,sha256=E74hjyTFJRy6tCxyTPfsYoM0GmOKoY1To_eHatpPEjs,12320
11
+ docs/source/_static/images/anlz_pco2.svg,sha256=LAv_EZf72FbslKdHmuBy5ROWy8xKKkkXqp7mSk3WG20,8155
12
+ docs/source/_static/images/anlz_pcob.svg,sha256=w4NRLCj0EMMnQ2i6tnlE-nFxETSiTs8PQF5nxMI3bgo,8682
13
+ docs/source/_static/images/anlz_pcp2.svg,sha256=JDncv7fo6ylorWyaH7kZ6roYB7zryFbJE_HY4IfWG8E,24904
14
+ docs/source/_static/images/anlz_pcpt.svg,sha256=8PcHBFAHXrDnTyC_7rO0TM-5cGy8BfjIXWxDi0zEK8Y,16473
15
+ docs/source/_static/images/anlz_ppth.svg,sha256=-rPlwvBrrsYaL87GNI-WJJBFCYkcZwkWoGwH5NT8bLk,7124
16
+ docs/source/_static/images/anlz_pqt2.svg,sha256=sRvbV-DjoTIWtxwChaw6kBqKFXNqBCF4V9hru9ZtdiE,18386
17
+ docs/source/_static/images/anlz_pqt2_2.svg,sha256=8g3XCJDz8xbH_KK7CvMpmIZjaGrWBKQVlA4k4M3PF50,13774
18
+ docs/source/_static/images/anlz_pqtz.svg,sha256=OPlW76hPkbhOByYgmVQfucTpb5irZXYcASZokfpK3A0,8141
19
+ docs/source/_static/images/anlz_pssi.svg,sha256=zM8fsNshSMd0wDvO_qM7RzsAKVIEN8Y41jY76hBJjyQ,11684
20
+ docs/source/_static/images/anlz_pssi_entry.svg,sha256=YkUg7fXtUst0OBI6vIlhHCK-nzhmR-22UGKFQ0pHxxA,12095
21
+ docs/source/_static/images/anlz_pvbr.svg,sha256=7mA6S__F4AoX-GamftlevellDiTlcIWRWi77A7HZEDg,7284
22
+ docs/source/_static/images/anlz_pwav.svg,sha256=psm3g7ILLVNgPNmPG2Hy7k9-q7qmnMnyQ3_-XYVAW2g,7566
23
+ docs/source/_static/images/anlz_pwv3.svg,sha256=FwZJIiHj5hj-FB-cioNlxrGBeIEuZy2BOLEzSWC57IE,8389
24
+ docs/source/_static/images/anlz_pwv4.svg,sha256=k5GQu2N965zPSG0FQWuml96UMfqBvs8Vy-Emklvn2dI,8173
25
+ docs/source/_static/images/anlz_pwv5.svg,sha256=Ph1TSlji2BgWK3rVI7MvnpGM68wef1s-Hs6OlmC0oQQ,8173
26
+ docs/source/_static/images/anlz_pwv5_entry.svg,sha256=TyNh_6gIsQKuDvS0RO5tmsptzmxiQ-LL1L3DQdIFi4I,5648
27
+ docs/source/_static/images/anlz_pwv6.svg,sha256=J2KvE_bI-NTFSpFp7QsKSsgbWGCyuUAuW_dOgdnv2DY,7577
28
+ docs/source/_static/images/anlz_pwv7.svg,sha256=n5NsTErvfsCw-otWdVXn12lM0tHyh-zQDZsZtQOJf0U,8173
29
+ docs/source/_static/images/anlz_pwvc.svg,sha256=wO_tsxRDVnAbptbSZYK1z7YOH-0vNUqXDB9Udj4ungA,7128
30
+ docs/source/_static/images/anlz_tag.svg,sha256=owPTtrTRDYCkaSg32_UJ-MTZp3K6WlkRQdJHTzdtxqs,6188
31
+ docs/source/_static/logos/dark/logo_primary.svg,sha256=pH4Q28u9JaCA4dVrKB-NDy00PNw2EhDwz7fIYUzezvk,3079
32
+ docs/source/_static/logos/light/logo_primary.svg,sha256=MUbYjowOcWdtoHVnK7pyV9xcTRdm7gS4whw94pVMGSI,3080
33
+ docs/source/_static/logos/mid/logo_primary.svg,sha256=utCWWbXXW5auRRposMMaMO9t_AflsN13rZGo3_Pg2WE,3078
34
+ docs/source/_templates/apidoc/module.rst_t,sha256=keQqb9G8LA-JS5tuytgzfsxRXsADEvuOz3mJjY9qYHU,185
35
+ docs/source/_templates/apidoc/package.rst_t,sha256=Ra-lunZcMyDtrb6vgrfAnBcBW4eFMTEIyg12fSe9h4o,1173
36
+ docs/source/_templates/apidoc/toc.rst_t,sha256=6EfpNwcRXLa8Ae3NcovuSmV5PSBy5gP0t6XfphUYb7M,127
37
+ docs/source/_templates/autosummary/class.rst,sha256=5yRMWkUNDXGvQE1gGm6yisL1bX4BDOsaBPciNUs_6vA,579
38
+ docs/source/_templates/autosummary/module.rst,sha256=xSCcWeBqQdMgpese2OwFlvzSOMsPZKTBCLCwgD3OG4k,789
39
+ docs/source/development/changes.md,sha256=ys1_zvYKQWEak_mBXBUMLpO3CWriHpjSAg1-17mTk04,40
40
+ docs/source/development/contributing.md,sha256=7bERN03kHo2Wf0ZrgHYP4MfBSdFGmE_Kk0CxK0uay2I,43
41
+ docs/source/formats/anlz.md,sha256=P7FrPqbu3i8SlRKVV9IU9cOQajKJHOjLlAWHheDGN7c,21455
42
+ docs/source/formats/db6.md,sha256=fuB1_PaalHV4zRkhXh5_mn1ZgHfPnSu_OzUOYbVls6w,30121
43
+ docs/source/formats/mysetting.md,sha256=OIYIn_B2HJcJtJM0wNjjzPy3xPkyz4raxg_-Y6E3D1k,9746
44
+ docs/source/formats/xml.md,sha256=uL7eWWQfQXsyf55Vydt1iLq7Jzio3kIGbSfjpg5-CRY,8491
45
+ docs/source/tutorial/anlz.md,sha256=JtcQIy44qaSP_GL0aL9VYQYOX8aVju7uFxgN3FPNy9o,145
46
+ docs/source/tutorial/configuration.md,sha256=mqmeFM3I7pDA_ajPQWqVyvc9Y0PWGYXLr3yyojTGsL8,2127
47
+ docs/source/tutorial/db6.md,sha256=wzfHAxSQ5rmYSDs6S0WheDwjkdFYsY3JxM0jGmXJcVU,6730
48
+ docs/source/tutorial/index.md,sha256=zjx5uXESYzHIyeqtL4XjhZUDPOpNiutZ3gkUOeG8R9w,318
49
+ docs/source/tutorial/mysetting.md,sha256=su-WMvEd3DrFF32RDfQBm3TlAUgK5PRPT1n_77BYnvY,3659
50
+ docs/source/tutorial/xml.md,sha256=x_rFvsVzZtUV0upEkPWkgrmr3B-FYjZKWvBuJBLFAu0,6414
51
+ pyrekordbox/__init__.py,sha256=jNxA9t2zA1P6zuOaQ9T_Nc5QW-G_KpUWymLNKOkPf7s,645
52
+ pyrekordbox/__main__.py,sha256=dm9QQMM5xPXzvFsF1ghwBor8ThUvZuC73As8c6kMZrA,6040
53
+ pyrekordbox/_version.py,sha256=RrHB9KG1O3GPm--rbTedqmZbdDrbgeRLXBmT4OBUqqI,411
54
+ pyrekordbox/config.py,sha256=eifT5Wejkk9zbQQ6190h-rF5WiF9foc55SNCHaYJKZk,21848
55
+ pyrekordbox/logger.py,sha256=qCY_3L_3WIMAvNmVKvh7oshGlWrd0T5aIW1bNCIu3Lo,521
56
+ pyrekordbox/utils.py,sha256=sT1xt-rM94Dir-S0OSSqKbquf9kdFbzZvBu3-7q0QDM,4361
57
+ pyrekordbox/xml.py,sha256=0qsKc8SQa54URwWGt6oHTOHBiAQU2S8MbSxDbzKUB7A,38369
58
+ pyrekordbox/anlz/__init__.py,sha256=OSpyl3pmmnt3mG905J9qvNnYchsgrIfathw0QXoOhjo,3156
59
+ pyrekordbox/anlz/file.py,sha256=i6Cnz-XpT47ClGhC3ZCCrgKhlk685NTM0hWyOuATNaE,6949
60
+ pyrekordbox/anlz/structs.py,sha256=Lt4fkb3SAE8w146eWeWGnpgRoP6jhLMWrSMoMwPjG04,7925
61
+ pyrekordbox/anlz/tags.py,sha256=uqneBP9CstYy7a4IpeEC9A5tfI59DQm0ik7yFXk_dNo,14094
62
+ pyrekordbox/db6/__init__.py,sha256=5XbfEIZuribNXIHMn9M4qw91cKHouH82PPJbmODhdgo,849
63
+ pyrekordbox/db6/aux_files.py,sha256=t0SfMCXBHjlCSfgRfpw1RTj0cmV0QY07xSLepjQqMnY,7591
64
+ pyrekordbox/db6/database.py,sha256=FlLeJgTp3-kt63_q5uod9Hzb5u83Z34Vhb9qI_qnYVM,74218
65
+ pyrekordbox/db6/registry.py,sha256=9GwMDKMqtm3-4nZ7mdzbL6XezPcVJUVqjHXGHKG_Jn4,9556
66
+ pyrekordbox/db6/tables.py,sha256=5ijJyO0OERbj1Usc__2QLRc7lPZ6FYluOXbajaEqzqk,65949
67
+ pyrekordbox/mysettings/__init__.py,sha256=rMS6Kknf1-X3PXF_TUxm8xui0H1Ap3R7G_9mqlnQUAM,705
68
+ pyrekordbox/mysettings/file.py,sha256=dQEsdBivpverXCssyVNUeQyUXIooNrOGIIiPLwi_6T4,12669
69
+ pyrekordbox/mysettings/structs.py,sha256=5Y1F3qTmsP1fRB39_BEHpQVxKx2DO9BytEuJUG_RNcY,8472
70
+ tests/__init__.py,sha256=s321RtRiHJsLt0YFq0NRncW7u66uwYHQE-6IjPcg10o,67
71
+ tests/test_anlz.py,sha256=hZAW5GFEJpU8UmmHYLuU5OZlApGrWZyPAR79KtaR0cw,5782
72
+ tests/test_config.py,sha256=wYp4wHRhIk2grhGXhk4z9umrhOWhVvTOm2mHydzLEs8,6157
73
+ tests/test_db6.py,sha256=6GUH_B1UAYfnJtjTK20cdCjP6hz_kDRR4KklvsnuAXg,37488
74
+ tests/test_mysetting.py,sha256=te6B_BErGc6ThgBpAl45n0Nd0hWJcm-X9WTw14bR6MA,5781
75
+ tests/test_xml.py,sha256=CbdzWgVjLwmxdaUqLo58QN4iRRaUdiJe_ni8y37BVfQ,16852
76
+ pyrekordbox-0.2.2.dist-info/LICENSE,sha256=Au8sngdQ79q5JsZXZJVU13j46VDxFbPwC4x6sSz8Jes,1074
77
+ pyrekordbox-0.2.2.dist-info/METADATA,sha256=jihtF6wZM3UKWuzrGLAxL-oOv4HjGyp-mwMbxdXmSB8,16672
78
+ pyrekordbox-0.2.2.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
79
+ pyrekordbox-0.2.2.dist-info/top_level.txt,sha256=Cv8QDfcJ7y8fYUm0Q8D5GoiGxaqb7qt8Z5ntVbj1cLk,23
80
+ pyrekordbox-0.2.2.dist-info/RECORD,,
@@ -1,5 +1,3 @@
1
- build
2
- dist
3
1
  docs
4
2
  pyrekordbox
5
3
  tests