symuid-import-cmus 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import datetime as dt
  4. import os
  5. import re
  6. import symuid
  7. # import symuid.library.cmus
  8. import sys
  9. FILE_PREFIX = b'CTC'
  10. VERSION_LENGTH = 1
  11. SUPPORTED_VERSION = b'\x0c'
  12. # always big endian, see cache_init()
  13. FLAGS_BYTEORDER = 'big'
  14. FLAGS_LENGTH = 4
  15. FLAG_64_BIT = 0x01
  16. RESERVED_PAD_REGEX = rb'\xff{16,}'
  17. STRING_TERMINATOR = b'\x00'
  18. def _int_from_bytes_sys(data_bytes):
  19. return int.from_bytes(data_bytes, byteorder=sys.byteorder)
  20. class Track:
  21. def __init__(self, cache_size, cache_bytes):
  22. """
  23. struct cache_entry {
  24. // size of this struct including size itself
  25. uint32_t size;
  26. int32_t play_count;
  27. int64_t mtime;
  28. int32_t duration;
  29. int32_t bitrate;
  30. int32_t bpm;
  31. uint8_t _reserved[CACHE_ENTRY_RESERVED_SIZE];
  32. // filename, codec, codec_profile and N * (key, val)
  33. char strings[];
  34. };
  35. """
  36. assert len(cache_bytes) + 4 == cache_size
  37. self._play_count = _int_from_bytes_sys(cache_bytes[0:4])
  38. # self._mtime = _int_from_bytes_sys(cache_bytes[4:12])
  39. # self._duration_seconds = _int_from_bytes_sys(cache_bytes[12:16])
  40. # self._bitrate = _int_from_bytes_sys(cache_bytes[16:20])
  41. # self._bpm = _int_from_bytes_sys(cache_bytes[20:24])
  42. strings = re.split(RESERVED_PAD_REGEX, cache_bytes)[1] \
  43. .split(STRING_TERMINATOR)
  44. self._path = strings[0]
  45. @property
  46. def path(self):
  47. return self._path
  48. @property
  49. def play_count(self):
  50. return self._play_count
  51. def import_track(cmus_track):
  52. if cmus_track.play_count > 0:
  53. print(cmus_track.play_count, cmus_track.path)
  54. def symuid_import_cmus(cache_path):
  55. with open(os.path.expanduser(cache_path), 'rb') as cache:
  56. # see cache.c cache_init()
  57. assert cache.read(len(FILE_PREFIX)) == FILE_PREFIX
  58. cache_version = cache.read(VERSION_LENGTH)
  59. assert cache_version == SUPPORTED_VERSION, cache_version
  60. flags = int.from_bytes(
  61. cache.read(FLAGS_LENGTH),
  62. byteorder=FLAGS_BYTEORDER, # persistent
  63. )
  64. assert flags & FLAG_64_BIT
  65. # support no other flags
  66. assert flags & ~FLAG_64_BIT == 0, flags
  67. # size includes size itself
  68. while True:
  69. size = _int_from_bytes_sys(cache.read(4))
  70. if size == 0:
  71. break
  72. cmus_track = Track(size, cache.read(size - 4))
  73. import_track(cmus_track)
  74. # see cache.c write_ti ALIGN
  75. cache.read((-size) % 8)
  76. def _init_argparser():
  77. import argparse
  78. argparser = argparse.ArgumentParser(description=None)
  79. argparser.add_argument(
  80. 'cache_path',
  81. nargs='?',
  82. default='~/.config/cmus/cache',
  83. help='(default: %(default)r)',
  84. )
  85. return argparser
  86. def main(argv):
  87. argparser = _init_argparser()
  88. args = argparser.parse_args(argv[1:])
  89. symuid_import_cmus(**vars(args))
  90. return 0
  91. if __name__ == "__main__":
  92. sys.exit(main(sys.argv))