#!/usr/bin/env python3 # -*- coding: utf-8 -*- import datetime as dt import os import re import symuid # import symuid.library.cmus import sys FILE_PREFIX = b'CTC' VERSION_LENGTH = 1 SUPPORTED_VERSION = b'\x0c' # always big endian, see cache_init() FLAGS_BYTEORDER = 'big' FLAGS_LENGTH = 4 FLAG_64_BIT = 0x01 RESERVED_PAD_REGEX = rb'\xff{16,}' STRING_TERMINATOR = b'\x00' def _int_from_bytes_sys(data_bytes): return int.from_bytes(data_bytes, byteorder=sys.byteorder) class Track: def __init__(self, cache_size, cache_bytes): """ struct cache_entry { // size of this struct including size itself uint32_t size; int32_t play_count; int64_t mtime; int32_t duration; int32_t bitrate; int32_t bpm; uint8_t _reserved[CACHE_ENTRY_RESERVED_SIZE]; // filename, codec, codec_profile and N * (key, val) char strings[]; }; """ assert len(cache_bytes) + 4 == cache_size self._play_count = _int_from_bytes_sys(cache_bytes[0:4]) # self._mtime = _int_from_bytes_sys(cache_bytes[4:12]) # self._duration_seconds = _int_from_bytes_sys(cache_bytes[12:16]) # self._bitrate = _int_from_bytes_sys(cache_bytes[16:20]) # self._bpm = _int_from_bytes_sys(cache_bytes[20:24]) strings = re.split(RESERVED_PAD_REGEX, cache_bytes)[1] \ .split(STRING_TERMINATOR) self._path = strings[0] @property def path(self): return self._path @property def play_count(self): return self._play_count def import_track(cmus_track): if cmus_track.play_count > 0: print(cmus_track.play_count, cmus_track.path) def symuid_import_cmus(cache_path): with open(os.path.expanduser(cache_path), 'rb') as cache: # see cache.c cache_init() assert cache.read(len(FILE_PREFIX)) == FILE_PREFIX cache_version = cache.read(VERSION_LENGTH) assert cache_version == SUPPORTED_VERSION, cache_version flags = int.from_bytes( cache.read(FLAGS_LENGTH), byteorder=FLAGS_BYTEORDER, # persistent ) assert flags & FLAG_64_BIT # support no other flags assert flags & ~FLAG_64_BIT == 0, flags # size includes size itself while True: size = _int_from_bytes_sys(cache.read(4)) if size == 0: break cmus_track = Track(size, cache.read(size - 4)) import_track(cmus_track) # see cache.c write_ti ALIGN cache.read((-size) % 8) def _init_argparser(): import argparse argparser = argparse.ArgumentParser(description=None) argparser.add_argument( 'cache_path', nargs='?', default='~/.config/cmus/cache', help='(default: %(default)r)', ) return argparser def main(argv): argparser = _init_argparser() args = argparser.parse_args(argv[1:]) symuid_import_cmus(**vars(args)) return 0 if __name__ == "__main__": sys.exit(main(sys.argv))