Module serato_data.history

Parser for Serato history and session files.

Classes

class HistoryFile (filename: str)
Expand source code
class HistoryFile(Parser):
    """\
    Represents the overall Serato history.  Entries in this file correspond to
    individual sessions.
    """

    DATACLASS = Session
    FIELDS = {
        b'\x00\x00\x00\x01': ('session_id', Parser.parse_field_int, None),
        b'\x00\x00\x00)': ('date', Parser.parse_field_str, None),
        b'\x00\x00\x009': ('software', Parser.parse_field_str, lambda s: f'Serato {s}' if s else None),
        b'\x00\x00\x00:': ('software_build', Parser.parse_field_int, None),
        b'\x00\x00\x00?': ('hardware', Parser.parse_field_str, None),
        b'\x00\x00\x00*': ('collapsed', Parser.parse_field_bool, None),
        b'\x00\x00\x00+': ('start', Parser.parse_field_arrow, None),
        b'\x00\x00\x00,': ('end', Parser.parse_field_arrow, None),
        b'\x00\x00\x00-': False,
        b'\x00\x00\x00.': False,
        b'\x00\x00\x00/': False,
        b'\x00\x00\x000': False,
        b'\x00\x00\x001': False,
        b'\x00\x00\x002': False,
        b'\x00\x00\x003': False,
        b'\x00\x00\x004': False,
        b'\x00\x00\x005': False,
        b'\x00\x00\x006': False,
        b'\x00\x00\x007': False,
        b'\x00\x00\x008': False,
        b'\x00\x00\x00;': False,
        b'\x00\x00\x00<': False,
        b'\x00\x00\x00=': False,
        b'\x00\x00\x00>': False,
        b'\x00\x00\x00@': False,
        b'\x00\x00\x00A': False,
        b'\x00\x00\x00B': False,
        b'\x00\x00\x00C': False,
        b'\x00\x00\x00D': False,
        b'\x00\x00\x00E': False,
        b'\x00\x00\x00F': False,
        b'\x00\x00\x00G': False,
        b'\x00\x00\x00H': False,
    }
    SKIP_CTYPES = [b'ocol']
    MAIN_CTYPE = b'oses'

    def make_dataclass_args(self, *args, **kwargs):
        sess_filename = os.path.join(os.path.dirname(self.filename), 'Sessions', f"{kwargs['session_id']}.session")
        return ([sess_filename] + list(args)), kwargs

    def loaded(self):
        self.data = list(sorted(self.data, key=lambda s: s.start))

Represents the overall Serato history. Entries in this file correspond to individual sessions.

Create a new parser for the given filename

Ancestors

Inherited members

class Session (filename: dataclasses.InitVar[str],
session_id: int,
date: str,
software: str | None = None,
software_build: int | None = None,
hardware: str | None = None,
collapsed: bool = True,
start: arrow.arrow.Arrow | None = None,
end: arrow.arrow.Arrow | None = None)
Expand source code
@dataclass
class Session:
    """\
    Represents a single Serato session (period during which Serato was open) and
    provides access to the songs played within that session.

    Session entries (songs) are the data parsed out of the session file, iterate
    through the instance to retrieve them.
    """

    filename: InitVar[str]

    session_id: int
    """ID of this session"""
    date: str
    """Date on which this session was started"""
    software: t.Optional[str] = None
    """Software used for this session"""
    software_build: t.Optional[int] = None
    """Build number of software used for this session"""
    hardware: t.Optional[str] = None
    """Primary hardware used for this session"""
    collapsed: bool = True
    """\
    Use case is not clear; typically the most recent session will be False and
    all other sessions are True
    """
    start: t.Optional[arrow.arrow.Arrow] = None
    """Time at which this session started"""
    end: t.Optional[arrow.arrow.Arrow] = None
    """Time at which this session ended"""

    def __post_init__(self, filename):
        self._session_filename = filename

    @property
    def _session_file(self):
        if not hasattr(self, '_session_file_obj'):
            self._session_file_obj = SessionFile(self._session_filename)
        return self._session_file_obj
    

    def __iter__(self):
        return iter(self._session_file)

Represents a single Serato session (period during which Serato was open) and provides access to the songs played within that session.

Session entries (songs) are the data parsed out of the session file, iterate through the instance to retrieve them.

Instance variables

var collapsed : bool

Use case is not clear; typically the most recent session will be False and all other sessions are True

var date : str

Date on which this session was started

var end : arrow.arrow.Arrow | None

Time at which this session ended

var filename : dataclasses.InitVar[str]

The type of the None singleton.

var hardware : str | None

Primary hardware used for this session

var session_id : int

ID of this session

var software : str | None

Software used for this session

var software_build : int | None

Build number of software used for this session

var start : arrow.arrow.Arrow | None

Time at which this session started

class SessionEntry (entry_id: int,
full_path: str | None = None,
location: str | None = None,
filename: str | None = None,
title: str | None = None,
artist: str | None = None,
album: str | None = None,
genre: str | None = None,
length: int | None = None,
size: int | None = None,
bitrate: int | None = None,
frequency: int | None = None,
bpm: int | None = None,
comments: str | None = None,
lang: str | None = None,
grouping: str | None = None,
remixer: str | None = None,
label: str | None = None,
composer: str | None = None,
year: str | None = None,
start: arrow.arrow.Arrow | None = None,
end: arrow.arrow.Arrow | None = None,
deck: int | None = None,
preview: bool = False,
playtime: int | None = None,
session_id: int | None = None,
played: bool = False,
raw_key: str | None = None,
key: camelot_key.Key | None = None,
added: arrow.arrow.Arrow | None = None,
updated_at: arrow.arrow.Arrow | None = None,
hardware: str | None = None,
comment_name: str | None = None,
rejected: bool = False,
beatport_id: int | None = None,
beatport_url: str | None = None)
Expand source code
@dataclass
class SessionEntry:
    """Represents an entry within a Serato session"""

    entry_id: int
    """ID of this entry"""
    full_path: t.Optional[str] = None
    """Path to the filename that was loaded.  May be in other formats if the file was loaded from somewhere other than disk (like a streaming service)"""
    location: t.Optional[str] = None
    filename: t.Optional[str] = None
    title: t.Optional[str] = None
    """Song title"""
    artist: t.Optional[str] = None
    """Song artist"""
    album: t.Optional[str] = None
    """Song album"""
    genre: t.Optional[str] = None
    """Song genre"""
    length: t.Optional[int] = None
    """\
    Length of the song, in seconds
    .. warning:: Unsure on units
    """
    size: t.Optional[int] = None
    """Size of the loaded file"""
    bitrate: t.Optional[int] = None
    """Audio file bitrate"""
    frequency: t.Optional[int] = None
    """Audio file sample frequency"""
    bpm: t.Optional[int] = None
    """Song BPM"""
    comments: t.Optional[str] = None
    lang: t.Optional[str] = None
    grouping: t.Optional[str] = None
    remixer: t.Optional[str] = None
    """Song remixer"""
    label: t.Optional[str] = None
    """Song label"""
    composer: t.Optional[str] = None
    """Song composer"""
    year: t.Optional[str] = None
    """Release year of the song"""
    start: t.Optional[arrow.arrow.Arrow] = None
    """Time at which the song was loaded into the deck"""
    end: t.Optional[arrow.arrow.Arrow] = None
    """Time at which the song was unloaded from the deck"""
    deck: t.Optional[int] = None
    """Deck into which the song was loaded"""
    preview: bool = False
    """\
    True if the song was loaded into the deck and previewed via cueing, but not actually played audibly
    .. warning:: Unsure on this
    """
    playtime: t.Optional[int] = None
    """\
    Duration of the time period the song was played, in seconds
    .. warning:: Unsure on units
    .. warning:: May be entire duration song was loaded (end - start)
    """
    session_id: t.Optional[int] = None
    """ID of the session to which this entry belongs"""
    played: bool = False
    """\
    Whether the song was played audibly or not
    .. warning:: Could use verification
    """
    raw_key: t.Optional[str] = None
    """Key of the song, as present in the data file"""
    key: t.Optional[Key] = None
    """Parsed key of the song, if one was present and can be parsed"""
    added: t.Optional[arrow.arrow.Arrow] = None
    updated_at: t.Optional[arrow.arrow.Arrow] = None
    hardware: t.Optional[str] = None
    """Hardware on which the deck was loaded"""
    comment_name: t.Optional[str] = None
    rejected: bool = False
    """\
    True if the song was loaded but not played audibly
    .. warning:: Unsure on this, may be set to True if the song was played even if it was not audible
    """
    # Generated later; included here for defaults
    beatport_id: t.Optional[int] = None
    """ID of the track on Beatport, if loaded via streaming"""
    beatport_url: t.Optional[str] = None
    """URL to the track on Beatport, if loaded via streaming"""

    def __post_init__(self, *args, **kwargs):
        match = re.match(r'^_(\d+)\.beatport', self.full_path or '')
        if match:
            self.beatport_id = int(match.group(1))
            self.beatport_url = f'https://www.beatport.com/track/unknown/{self.beatport_id}'

        if self.raw_key:
            try:
                self.key = parse(self.raw_key)
            except:
                # TODO: maybe log key parsing failure
                pass

    @property
    def url(self) -> t.Optional[str]:
        """\
        URL to the song if loaded from a streaming service.
        """
        return self.beatport_url or None

Represents an entry within a Serato session

Instance variables

var added : arrow.arrow.Arrow | None

The type of the None singleton.

var album : str | None

Song album

var artist : str | None

Song artist

var beatport_id : int | None

ID of the track on Beatport, if loaded via streaming

var beatport_url : str | None

URL to the track on Beatport, if loaded via streaming

var bitrate : int | None

Audio file bitrate

var bpm : int | None

Song BPM

var comment_name : str | None

The type of the None singleton.

var comments : str | None

The type of the None singleton.

var composer : str | None

Song composer

var deck : int | None

Deck into which the song was loaded

var end : arrow.arrow.Arrow | None

Time at which the song was unloaded from the deck

var entry_id : int

ID of this entry

var filename : str | None

The type of the None singleton.

var frequency : int | None

Audio file sample frequency

var full_path : str | None

Path to the filename that was loaded. May be in other formats if the file was loaded from somewhere other than disk (like a streaming service)

var genre : str | None

Song genre

var grouping : str | None

The type of the None singleton.

var hardware : str | None

Hardware on which the deck was loaded

var key : camelot_key.Key | None

Parsed key of the song, if one was present and can be parsed

var label : str | None

Song label

var lang : str | None

The type of the None singleton.

var length : int | None

Length of the song, in seconds

Warning: Unsure on units

var location : str | None

The type of the None singleton.

var played : bool

Whether the song was played audibly or not

Warning: Could use verification

var playtime : int | None

Duration of the time period the song was played, in seconds

Warning: Unsure on units

Warning: May be entire duration song was loaded (end - start)

var preview : bool

True if the song was loaded into the deck and previewed via cueing, but not actually played audibly

Warning: Unsure on this

var raw_key : str | None

Key of the song, as present in the data file

var rejected : bool

True if the song was loaded but not played audibly

Warning: Unsure on this, may be set to True if the song was played even if it was not audible

var remixer : str | None

Song remixer

var session_id : int | None

ID of the session to which this entry belongs

var size : int | None

Size of the loaded file

var start : arrow.arrow.Arrow | None

Time at which the song was loaded into the deck

var title : str | None

Song title

var updated_at : arrow.arrow.Arrow | None

The type of the None singleton.

prop url : str | None
Expand source code
@property
def url(self) -> t.Optional[str]:
    """\
    URL to the song if loaded from a streaming service.
    """
    return self.beatport_url or None

URL to the song if loaded from a streaming service.

var year : str | None

Release year of the song

class SessionFile (filename: str)
Expand source code
class SessionFile(Parser):
    """\
    Parses session entries out of a session data file.
    """

    DATACLASS = SessionEntry
    FIELDS = {
        b'\x00\x00\x00\x01': ('entry_id', Parser.parse_field_int, None),
        b'\x00\x00\x00\x02': ('full_path', Parser.parse_field_str, None),
        b'\x00\x00\x00\x03': ('location', Parser.parse_field_str, None),
        b'\x00\x00\x00\x04': ('filename', Parser.parse_field_str, None),
        b'\x00\x00\x00\x06': ('title', Parser.parse_field_str, None),
        b'\x00\x00\x00\x07': ('artist', Parser.parse_field_str, None),
        b'\x00\x00\x00\x08': ('album', Parser.parse_field_str, None),
        b'\x00\x00\x00\x09': ('genre', Parser.parse_field_str, None),
        b'\x00\x00\x00\x0a': ('length', Parser.parse_field_str, None),
        b'\x00\x00\x00\x0b': ('size', Parser.parse_field_str, None),
        b'\x00\x00\x00\x0d': ('bitrate', Parser.parse_field_str, None),
        b'\x00\x00\x00\x0e': ('frequency', Parser.parse_field_str, None),
        b'\x00\x00\x00\x0f': ('bpm', Parser.parse_field_int, None),
        b'\x00\x00\x00\x11': ('comments', Parser.parse_field_str, None),
        b'\x00\x00\x00\x12': ('lang', Parser.parse_field_str, None),
        b'\x00\x00\x00\x13': ('grouping', Parser.parse_field_str, None),
        b'\x00\x00\x00\x14': ('remixer', Parser.parse_field_str, None),
        b'\x00\x00\x00\x15': ('label', Parser.parse_field_str, None),
        b'\x00\x00\x00\x16': ('composer', Parser.parse_field_str, None),
        b'\x00\x00\x00\x17': ('year', Parser.parse_field_str, None),
        b'\x00\x00\x00\x1c': ('start', Parser.parse_field_arrow, None),
        b'\x00\x00\x00\x1d': ('end', Parser.parse_field_arrow, None),
        b'\x00\x00\x00\x1f': ('deck', Parser.parse_field_int, None),
        b'\x00\x00\x00\x27': ('preview', Parser.parse_field_bool, None),
        b'\x00\x00\x00\x2d': ('playtime', Parser.parse_field_int, None),
        b'\x00\x00\x00\x2f': ('session_id', Parser.parse_field_int, None),
        b'\x00\x00\x00\x32': ('played', Parser.parse_field_bool, None),
        b'\x00\x00\x00\x33': ('key', Parser.parse_field_str, None),
        b'\x00\x00\x00\x34': ('added', Parser.parse_field_bool, None),
        b'\x00\x00\x00\x35': ('updated_at', Parser.parse_field_arrow, None),
        b'\x00\x00\x00\x3f': ('hardware', Parser.parse_field_str, None),
        b'\x00\x00\x00\x40': ('comment_name', Parser.parse_field_str, None),
        b'\x00\x00\x00\x46': ('rejected', Parser.parse_field_bool, None),
        b'\x00\x00\x000': False,
        b'\x00\x00\x00D': False,
        b'\x00\x00\x00E': False,
        b'\x00\x00\x00H': False,
        b'\x00\x00\x00N': False,
    }
    MAIN_CTYPE = b'oent'

    def make_dataclass_args(self, *args, **kwargs):
        # Convert key to raw_key; then key is parsed back out if possible
        kwargs['raw_key'] = kwargs.pop('key', None)
        return args, kwargs

    def loaded(self):
        self.data = list(sorted({e.entry_id: e for e in self.data}.values(), key=lambda e: e.start))

Parses session entries out of a session data file.

Create a new parser for the given filename

Ancestors

Inherited members