Automatically migrated from Gitolite
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
7.4 KiB

7 years ago
  1. import subprocess, sys, os, random, string, re, time
  2. drive = sys.argv[1]
  3. target_path = sys.argv[2]
  4. if sys.argv[3] == "--ddrescue":
  5. forced_ddrescue = True
  6. else:
  7. forced_ddrescue = False
  8. def mount_drive(drive):
  9. output = subprocess.check_output(["udisksctl", "mount", "-b", drive])
  10. # urgh, regex to extract the mountpath...
  11. match = re.search("Mounted [^ ]+ at (.+)\.$", output, re.MULTILINE)
  12. if match is None:
  13. raise Exception("Drive failed to mount.")
  14. else:
  15. return match.group(1)
  16. def unmount_drive(drive):
  17. retcode = subprocess.call(["udisksctl", "unmount", "-b", drive])
  18. if retcode != 0:
  19. raise Exception("Drive failed to unmount.")
  20. def get_disc_info(drive):
  21. return [line.strip() for line in subprocess.check_output(["udevadm", "info", "-q", "env", "-n", drive]).splitlines()]
  22. def format_bytes(inp):
  23. amount, unit = inp.rsplit(" ", 1)
  24. map_ = {
  25. "B": 1,
  26. "KB": 1024,
  27. "MB": 1024 * 1024,
  28. "GB": 1024 * 1024 * 1024
  29. }
  30. return int(amount) * map_[unit.upper()]
  31. while True:
  32. name = raw_input("## What is the name of the next disc? ")
  33. # Try to unmount the drive, just in case it was auto-mounted
  34. try:
  35. unmount_drive(drive)
  36. except:
  37. # Doesn't matter, wasn't mounted to begin with.
  38. pass
  39. media_type = "unknown"
  40. print "## Waiting for media to be recognized... Please close the tray if it is still opened."
  41. while True:
  42. # This is a loop to wait until the disc is recognized...
  43. disc_info = get_disc_info(drive)
  44. if "ID_CDROM_MEDIA=1" in disc_info:
  45. break # Disc was recognized, end loop
  46. time.sleep(0.5)
  47. # Now determine the media type.
  48. if "ID_CDROM_MEDIA_CD=1" in disc_info or "ID_CDROM_MEDIA_CD_R=1" in disc_info:
  49. # Some kind of CD.
  50. data_tracks = 0
  51. audio_tracks = 0
  52. total_tracks = 0
  53. for line in disc_info:
  54. key, value = line.split("=", 1)
  55. if key == "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO":
  56. audio_tracks = int(value)
  57. elif key == "ID_CDROM_MEDIA_TRACK_COUNT_DATA":
  58. data_tracks = int(value)
  59. elif key == "ID_CDROM_MEDIA_TRACK_COUNT":
  60. total_tracks = int(value)
  61. if total_tracks != (data_tracks + audio_tracks):
  62. print "## ERROR: Unrecognized tracks found on CD! Please report this as a bug."
  63. continue # Abort imaging cycle
  64. if data_tracks == 0 and audio_tracks > 0:
  65. media_type = "cd-audio"
  66. elif data_tracks > 0 and audio_tracks == 0:
  67. media_type = "cd-data"
  68. elif data_tracks > 0 and audio_tracks > 0:
  69. media_type = "cd-mixed"
  70. else:
  71. media_type = "cd-blank"
  72. elif "ID_CDROM_MEDIA_DVD=1" in disc_info:
  73. # We cannot distinguish between a Video-DVD and a DVD-ROM from the udev data alone.
  74. try:
  75. mount_path = mount_drive(drive)
  76. except subprocess.CalledProcessError, e:
  77. # Could not mount; try to unmount and mount again.
  78. print "## WARNING: Drive already mounted; re-mounting..."
  79. # Wait for a bit first, to let the auto-mount complete...
  80. time.sleep(1)
  81. unmount_drive(drive)
  82. mount_path = mount_drive(drive)
  83. if os.path.exists(os.path.join(mount_path, "VIDEO_TS")):
  84. media_type = "dvd-video"
  85. elif os.path.exists(os.path.join(mount_path, "AUDIO_TS")):
  86. media_type = "dvd-audio"
  87. else:
  88. media_type = "dvd-data"
  89. # Apparently some discs will not work correctly, if we don't wait before unmounting...
  90. time.sleep(1)
  91. unmount_drive(drive)
  92. # Now we'll do something, depending on the media type.
  93. if media_type == "unknown":
  94. print "## ERROR: Failed to determine media type for disc."
  95. continue # abort
  96. if media_type == "cd-blank": # TODO: Figure out blank-ness of a DVD.
  97. print "## ERROR: Cannot image blank disc!"
  98. continue # abort
  99. type_map = {
  100. "cd-audio": "Audio CD",
  101. "cd-data": "Data CD(-ROM)",
  102. "cd-mixed": "CD(-ROM) with non-data (eg. audio) tracks",
  103. "dvd-video": "Video DVD",
  104. "dvd-audio": "Audio DVD",
  105. "dvd-data": "Data DVD-ROM"
  106. }
  107. print "## Disc detected as %s" % type_map[media_type]
  108. if media_type in ("cd-data", "cd-mixed") and not forced_ddrescue:
  109. bin_path = os.path.join(target_path, "%s.bin" % name)
  110. cue_path = os.path.join(target_path, "%s.cue" % name)
  111. tmp_path = "/tmp/image-%s" % ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))
  112. print "## Starting imaging process..."
  113. tries = 0
  114. success = False
  115. # TODO: Read output to scan for "Device or resource busy"
  116. while True:
  117. try:
  118. unmount_drive(drive)
  119. except:
  120. # Doesn't matter, wasn't mounted to begin with.
  121. pass
  122. retcode = subprocess.call([
  123. "cdrdao", "read-cd",
  124. "--device", drive,
  125. "--read-raw",
  126. "--datafile", bin_path,
  127. "-v", "2",
  128. tmp_path
  129. ])
  130. if retcode != 0:
  131. if tries < 2:
  132. print "## WARNING: Imaging failed, retrying in a second..."
  133. time.sleep(1)
  134. tries += 1
  135. continue # Retry
  136. else:
  137. print "## ERROR: IMAGING FAILED."
  138. success = False
  139. break
  140. else:
  141. print "## Imaging finished successfully."
  142. success = True
  143. break
  144. if success == False:
  145. continue # Abort
  146. print "## Generating cuesheet..."
  147. retcode = subprocess.call([
  148. "toc2cue",
  149. tmp_path,
  150. cue_path
  151. ])
  152. if retcode != 0:
  153. print "## ERROR: CUESHEET CREATION FAILED."
  154. continue
  155. else:
  156. print "## Cuesheet creation finished successfully."
  157. if media_type == "cd-mixed":
  158. print "## WARNING: The disc contains audio tracks as well. You may want to consider using an Audio CD ripper for those."
  159. elif media_type == "cd-audio":
  160. print "## ERROR: Cannot currently image Audio CDs. Please use an Audio CD ripper instead."
  161. elif media_type in ("dvd-audio", "dvd-video", "dvd-data") or forced_ddrescue:
  162. # Create an ISO, that should be sufficient... use ddrescue by default.
  163. iso_path = os.path.join(target_path, "%s.iso" % name)
  164. log_path = os.path.join(target_path, "%s.ddrescuelog" % name)
  165. if media_type == "dvd-video":
  166. # We hook the stdout/stderr here, to detect large amounts of read errors for video-DVDs,
  167. # which is a likely indicator of ARccOS protection.
  168. # FIXME: Currently -completely- broken! Needs fixing ASAP.
  169. arccos = False
  170. proc = subprocess.Popen(["ddrescue", "-A", "-M", "-r", "20", "-b", "2048", drive, iso_path, log_path], stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1)
  171. while proc.poll() is None:
  172. line = proc.stdout.readline()
  173. sys.stdout.write(line)
  174. match = re.match("rescued:\s*([0-9]+ [A-Z]+),\s*errsize:\s*([0-9]+ [A-Z]+)", line)
  175. if match is not None and arccos == False:
  176. # Status line...
  177. rescued_bytes = format_bytes(match.group(1))
  178. error_bytes = format_bytes(match.group(2))
  179. if error_bytes > rescued_bytes and error_bytes > (1024 * 1024 * 10) and rescued_bytes > (1024 * 1024 * 10):
  180. # Only trigger if there's more read errors than read successes, and both
  181. # values exceed 10MB.
  182. print "## WARNING: Large amount of read errors detected, likely ARccOS-protected. Restarting with different parameters..."
  183. arccos = True
  184. # TODO: Figure out working parameters...
  185. #break
  186. if arccos == False:
  187. proc.communicate()
  188. retcode = proc.returncode
  189. else:
  190. os.remove(iso_path)
  191. os.remove(log_path)
  192. # TODO: Run with proper params...
  193. else:
  194. retcode = subprocess.call(["ddrescue", "-A", "-M", "-r", "20", "-b", "2048", drive, iso_path, log_path])
  195. if retcode in (1, 3):
  196. print "## ERROR: An error occurred in ddrescue."
  197. continue
  198. elif retcode == 2:
  199. print "## WARNING: ddrescue indicates corruption, image may have failed."
  200. # arccos bypass:
  201. # sdparm --set=RRC=0 /dev/sr0
  202. # It might've been automounted by something again...
  203. subprocess.call(["udisksctl", "unmount", "-b", drive])
  204. # Eject the drive
  205. subprocess.call(["eject", drive])