read.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. # greaseweazle/tools/read.py
  2. #
  3. # Greaseweazle control script: Read Disk to Image.
  4. #
  5. # Written & released by Keir Fraser <keir.xen@gmail.com>
  6. #
  7. # This is free and unencumbered software released into the public domain.
  8. # See the file COPYING for more details, or visit <http://unlicense.org>.
  9. description = "Read a disk to the specified image file."
  10. import sys
  11. import importlib
  12. from greaseweazle.tools import util
  13. from greaseweazle import error
  14. from greaseweazle import usb as USB
  15. from greaseweazle.flux import Flux
  16. def open_image(args):
  17. image_class = util.get_image_class(args.file)
  18. error.check(hasattr(image_class, 'to_file'),
  19. "%s: Cannot create %s image files"
  20. % (args.file, image_class.__name__))
  21. image = image_class.to_file(args.scyl, args.nr_sides)
  22. if args.rate is not None:
  23. image.bitrate = args.rate
  24. for opt, val in args.file_opts.items():
  25. error.check(hasattr(image, 'opts') and hasattr(image.opts, opt),
  26. "%s: Invalid file option: %s" % (args.file, opt))
  27. setattr(image.opts, opt, val)
  28. return image
  29. def normalise_rpm(flux, rpm):
  30. """Adjust all revolutions in Flux object to have specified rotation speed.
  31. """
  32. index_list, freq = flux.index_list, flux.sample_freq
  33. norm_to_index = 60/rpm * flux.sample_freq
  34. norm_flux = []
  35. to_index, index_list = index_list[0], index_list[1:]
  36. factor = norm_to_index / to_index
  37. for x in flux.list:
  38. to_index -= x
  39. if to_index >= 0:
  40. norm_flux.append(x*factor)
  41. continue
  42. if not index_list:
  43. break
  44. n_to_index, index_list = index_list[0], index_list[1:]
  45. n_factor = norm_to_index / n_to_index
  46. norm_flux.append((x+to_index)*factor - to_index*n_factor)
  47. to_index, factor = n_to_index, n_factor
  48. return Flux([norm_to_index]*len(flux.index_list), norm_flux, freq)
  49. def read_and_normalise(usb, args):
  50. flux = usb.read_track(args.revs)
  51. if args.rpm is not None:
  52. flux = normalise_rpm(flux, args.rpm)
  53. return flux
  54. class Formatter:
  55. def __init__(self):
  56. self.length = 0
  57. def print(self, s):
  58. self.erase()
  59. self.length = len(s)
  60. print(s, end="", flush=True)
  61. def erase(self):
  62. l = self.length
  63. print("\b"*l + " "*l + "\b"*l, end="", flush=True)
  64. self.length = 0
  65. def read_with_retry(usb, args, cyl, side, decoder):
  66. flux = read_and_normalise(usb, args)
  67. if decoder is None:
  68. return flux
  69. dat = decoder(cyl, side, flux)
  70. if dat.nr_missing() != 0:
  71. formatter = Formatter()
  72. for retry in range(3):
  73. formatter.print(" Retry %d" % (retry+1))
  74. flux = read_and_normalise(usb, args)
  75. dat.decode_raw(flux)
  76. if dat.nr_missing() == 0:
  77. break
  78. formatter.erase()
  79. return dat
  80. def read_to_image(usb, args, image, decoder=None):
  81. """Reads a floppy disk and dumps it into a new image file.
  82. """
  83. for cyl in range(args.scyl, args.ecyl+1):
  84. for side in range(0, args.nr_sides):
  85. print("\rReading Track %u.%u..." % (cyl, side), end="")
  86. usb.seek((cyl, cyl*2)[args.double_step], side)
  87. dat = read_with_retry(usb, args, cyl, side, decoder)
  88. image.append_track(dat)
  89. print()
  90. # Write the image file.
  91. with open(args.file, "wb") as f:
  92. f.write(image.get_image())
  93. def main(argv):
  94. parser = util.ArgumentParser(usage='%(prog)s [options] file')
  95. parser.add_argument("--device", help="greaseweazle device name")
  96. parser.add_argument("--drive", type=util.drive_letter, default='A',
  97. help="drive to read (A,B,0,1,2)")
  98. parser.add_argument("--format", help="disk format")
  99. parser.add_argument("--revs", type=int, default=3,
  100. help="number of revolutions to read per track")
  101. parser.add_argument("--scyl", type=int, default=0,
  102. help="first cylinder to read")
  103. parser.add_argument("--ecyl", type=int, default=81,
  104. help="last cylinder to read")
  105. parser.add_argument("--single-sided", action="store_true",
  106. help="single-sided read")
  107. parser.add_argument("--double-step", action="store_true",
  108. help="double-step drive heads")
  109. parser.add_argument("--rate", type=int, help="data rate (kbit/s)")
  110. parser.add_argument("--rpm", type=int, help="convert drive speed to RPM")
  111. parser.add_argument("file", help="output filename")
  112. parser.description = description
  113. parser.prog += ' ' + argv[1]
  114. args = parser.parse_args(argv[2:])
  115. args.nr_sides = 1 if args.single_sided else 2
  116. args.file, args.file_opts = util.split_opts(args.file)
  117. try:
  118. usb = util.usb_open(args.device)
  119. image = open_image(args)
  120. if not args.format and hasattr(image, 'default_format'):
  121. args.format = image.default_format
  122. decoder = None
  123. if args.format:
  124. mod = importlib.import_module('greaseweazle.codec.' + args.format)
  125. decoder = mod.__dict__['decode_track']
  126. try:
  127. util.with_drive_selected(read_to_image, usb, args, image,
  128. decoder=decoder)
  129. except:
  130. print()
  131. raise
  132. except USB.CmdError as error:
  133. print("Command Failed: %s" % error)
  134. if __name__ == "__main__":
  135. main(sys.argv)
  136. # Local variables:
  137. # python-indent: 4
  138. # End: