PageRenderTime 88ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/spk/sabnzbd/src/CharTranslator.py

https://github.com/Foncekar/spksrc
Python | 374 lines | 319 code | 4 blank | 51 comment | 4 complexity | 41368fde7ef0dcb95fda882145b484e3 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #!/usr/local/sabnzbd/env/bin/python -OO
  2. #-*- coding: iso-8859-15 -*-
  3. #
  4. # If a file has been archieved under an ISO-8859 environment and unarchived
  5. # under an UTF8 environment, then you will get an encoding format problem.
  6. # The file will not be readable through SAMBA.
  7. #
  8. # Renaming script for SABnzbd runnning under Synology NAS.
  9. # By default the NZB software is running under UTF-8 encoding format
  10. # in order to correctly handle the french accents (ιθΰη...) a SABnzbd
  11. # post-script must be run in order to convert the file encoding.
  12. #
  13. # To fix this problem, you must convert the encoding format
  14. # to the UTF8 (default Synology encoding)
  15. # The script is trying to detect if the original file/directory are coming
  16. # from a RAR archive. In this case the unrar command on your Syno system will
  17. # unpack in CP850 format (DOS).
  18. # NB: in all cases, files will be readable through samba, even if the detection
  19. # failed. But converted characters will not be good, ex: Ξ? instead ι
  20. #
  21. # Remark: I guess it should work for any other encoding style. Just replace
  22. # ISO-8859-15 (Western Europe) by the one coresponding to your country:
  23. # http://en.wikipedia.org/wiki/Character_encoding#Common_character_encodings
  24. #
  25. # Done by LapinFou
  26. # date | version | comment
  27. #--------------------------------------
  28. # 12-04-22 | 1.0 | Initial version
  29. # 12-04-22 | 1.1 | Change encoding to ISO-8859-15
  30. # | Added CP437 special characters (0x80-0x9A)
  31. # 12-04-24 | 1.2 | Mixed encoding is now supported
  32. # | UTF-8 encoding format detected
  33. # 12-04-24 | 1.3 | Fixed typo line 57 (test must be 0xA0, not 0xA1)
  34. # 12-05-24 | 1.4 | Added an exception for "Β" character
  35. # | Added 7z unpack
  36. # | Added move option
  37. # | Added Syno index option
  38. # 13-02-15 | 1.5 | Added an option to activate Sickbear post-processing
  39. # | More evoluate move option (merge is now managed)
  40. # | Argv1 folder is not renamed anymore (already managed by SABnzbd)
  41. # 13-02-18 | 1.6 | Argv1 folder is now renamed (not managed by SABnzbd)
  42. # 13-02-19 | 1.7 | Changed CP437 detection range
  43. # 13-02-19 | 1.8 | Changed CP437 DOS encoding style with CP850
  44. # 13-10-02 | 1.9 | Fixed an issue with some NZB and Sickbeard option
  45. # | In order to simplify the support, the script version is now displayed
  46. #
  47. # get library modules
  48. import sys
  49. import os
  50. import subprocess
  51. import shutil
  52. scriptVersionIs = 1.9
  53. # If empty, then no move
  54. # Format must be synology full path (case sensitive). For ex: /volume1/video/News
  55. MoveToThisFolder = ''
  56. # If MoveMergeSubFolder = False, then equivalent to unix command:
  57. # mv -rf srcFolder destFolder
  58. # In case of conflict between an already existing sub-folder in the destination folder:
  59. # the destination sub-folder will be replaced with source sub-folder
  60. #
  61. # If MoveMergeSubFolder = True, then equivalent to unix command:
  62. # cp -rf srcFolder/* destFolder/
  63. # rm -rf srcFolder
  64. # In case of conflict between an already existing sub-folder in the destination folder:
  65. # the destination sub-folder will be merged with source sub-folder (kind of incremental)
  66. MoveMergeSubFolder = True
  67. # /!\ IndexInSynoDLNA and SickBeardPostProcessing are exclusive
  68. # =============================================================
  69. # If "True", then the folder will be indexed into Synology DLNA
  70. # By default it is "False"
  71. IndexInSynoDLNA = False
  72. # If "True", the folder will be send to SickBeard for Post-Processing
  73. # By default it is "False"
  74. SickBeardPostProcessing = False
  75. # If "True", all .7z files will be unpacked then source .7z file will be deleted
  76. # By default it is "False"
  77. Unpack7z = True
  78. ########################
  79. # ----- Functions ---- #
  80. ########################
  81. # Special character hex range:
  82. # CP850: 0x80-0xA5 (fortunately not used in ISO-8859-15)
  83. # UTF-8: 1st hex code 0xC2-0xC3 followed by a 2nd hex code 0xA1-0xFF
  84. # ISO-8859-15: 0xA6-0xFF
  85. # The function will detect if fileDirName contains a special character
  86. # If there is special character, detects if it is a UTF-8, CP850 or ISO-8859-15 encoding
  87. def renameFunc(fullPath, fileDirName):
  88. encodingDetected = False
  89. # parsing all files/directories in odrer to detect if CP850 is used
  90. for Idx in range(len(fileDirName)):
  91. # /!\ detection is done 2char by 2char for UTF-8 special character
  92. if (len(fileDirName) != 1) & (Idx < (len(fileDirName) - 1)):
  93. # Detect UTF-8
  94. if ((fileDirName[Idx] == '\xC2') | (fileDirName[Idx] == '\xC3')) & ((fileDirName[Idx+1] >= '\xA0') & (fileDirName[Idx+1] <= '\xFF')):
  95. print os.path.join(fullPath, fileDirName) + " -> UTF-8 detected: Nothing to be done"
  96. encodingDetected = True
  97. break;
  98. # Detect CP850
  99. elif ((fileDirName[Idx] >= '\x80') & (fileDirName[Idx] <= '\xA5')):
  100. utf8Name = fileDirName.decode('cp850')
  101. utf8Name = utf8Name.encode('utf-8')
  102. os.rename(os.path.join(fullPath, fileDirName), os.path.join(fullPath, utf8Name))
  103. print os.path.join(fullPath, utf8Name) + " -> CP850 detected: Renamed"
  104. encodingDetected = True
  105. break;
  106. # Detect ISO-8859-15
  107. elif (fileDirName[Idx] >= '\xA6') & (fileDirName[Idx] <= '\xFF'):
  108. utf8Name = fileDirName.decode('iso-8859-15')
  109. utf8Name = utf8Name.encode('utf-8')
  110. os.rename(os.path.join(fullPath, fileDirName), os.path.join(fullPath, utf8Name))
  111. print os.path.join(fullPath, utf8Name) + " -> ISO-8859-15 detected: Renamed"
  112. encodingDetected = True
  113. break;
  114. else:
  115. # Detect CP850
  116. if ((fileDirName[Idx] >= '\x80') & (fileDirName[Idx] <= '\xA5')):
  117. utf8Name = fileDirName.decode('cp850')
  118. utf8Name = utf8Name.encode('utf-8')
  119. os.rename(os.path.join(fullPath, fileDirName), os.path.join(fullPath, utf8Name))
  120. print os.path.join(fullPath, utf8Name) + " -> CP850 detected: Renamed"
  121. encodingDetected = True
  122. break;
  123. # Detect ISO-8859-15
  124. elif (fileDirName[Idx] >= '\xA6') & (fileDirName[Idx] <= '\xFF'):
  125. utf8Name = fileDirName.decode('iso-8859-15')
  126. utf8Name = utf8Name.encode('utf-8')
  127. os.rename(os.path.join(fullPath, fileDirName), os.path.join(fullPath, utf8Name))
  128. print os.path.join(fullPath, utf8Name) + " -> ISO-8859-15 detected: Renamed"
  129. encodingDetected = True
  130. break;
  131. if (encodingDetected == False):
  132. print os.path.join(fullPath, fileDirName) + " -> No special characters detected: Nothing to be done"
  133. return
  134. # scan .7z files and unpack them
  135. def unpack7zFunc(DirName):
  136. print "Scanning for .7z file(s), then unpack them"
  137. print "Scanning files..."
  138. DetectedFiles = False
  139. for dirname, dirnames, filenames in os.walk(DirName):
  140. for filename in filenames:
  141. if (filename[-3:] == ".7z"):
  142. print "Unpack %s..." %(filename)
  143. DetectedFiles = True
  144. try:
  145. filepath = os.path.join(dirname, filename)
  146. syno7z_cmd = ['/usr/syno/bin/7z', 'x', '-y', filepath]
  147. p = subprocess.Popen(syno7z_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  148. out, err = p.communicate()
  149. if (str(out) != ''):
  150. print("7z result: " + filepath + " successfully unpacked")
  151. os.remove(filepath)
  152. print(filepath + " has been deleted")
  153. if (str(err) != ''):
  154. print("7z failed: " + str(err))
  155. except OSError, e:
  156. print("Unable to run 7z: "+str(e))
  157. if DetectedFiles:
  158. print "Scanning for .7z files Done !"
  159. else:
  160. print "No .7z file Detected !"
  161. return
  162. # add folder in the Syno index database (DLNA server)
  163. def addToSynoIndex(DirName):
  164. print "Adding folder in the DLNA server"
  165. synoindex_cmd = ['/usr/syno/bin/synoindex', '-A', DirName]
  166. try:
  167. p = subprocess.Popen(synoindex_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  168. out, err = p.communicate()
  169. if (str(out) == ''):
  170. print("synoindex result: " + DirName + " successfully added to Synology database")
  171. else:
  172. print("synoindex result: " + str(out))
  173. if (str(err) != ''):
  174. print("synoindex failed: " + str(err))
  175. except OSError, e:
  176. print("Unable to run synoindex: "+str(e))
  177. return
  178. ########################
  179. # --- Main Program --- #
  180. ########################
  181. print "Launching CharTranslator Python script (v%s) ..." %scriptVersionIs
  182. print ""
  183. # Get scripts directory of the SABnzbd from its config.ini file
  184. if (SickBeardPostProcessing == True):
  185. print 100*'-'
  186. print "SickBeardPostProcessing option is ON"
  187. print "Locating SABnzbd config.ini file..."
  188. # Get SABnzbd rundir folder
  189. currentFolder = os.getcwd()
  190. # SABnzbd config.ini location
  191. SabScriptsFolder = ''
  192. confFile = '../../var/config.ini'
  193. # Check that file does exit
  194. if os.path.isfile(confFile):
  195. SabConfigFile = open('../../var/config.ini', 'r')
  196. # Parse each lines in order to get scripts folder path
  197. for line in SabConfigFile.readlines():
  198. if line[:len('script_dir')] == 'script_dir':
  199. # Get script_dir result
  200. SabScriptsFolder = line.split('=')[1]
  201. # Remove 1st space + \n
  202. if (SabScriptsFolder[0] == ' '):
  203. SabScriptsFolder = SabScriptsFolder[1:]
  204. SabScriptsFolder = SabScriptsFolder.replace('\n', '')
  205. break
  206. SabConfigFile.close
  207. # Check that SABnzbd script folder has been found
  208. if (SabScriptsFolder == ''):
  209. print 100*'#'
  210. print "SABnzbd script_dir parameter not found!"
  211. print 100*'#'
  212. sys.exit(1)
  213. else:
  214. print "SABnzbd script_dir parameter is: '%s'" %SabScriptsFolder
  215. # Load SickBeard module
  216. SickBeardScript = os.path.join(SabScriptsFolder, 'autoProcessTV.py')
  217. # Check that SickBeard post-processing is present into SABnzbd scripts folder
  218. if os.path.isfile(SickBeardScript):
  219. sys.path.append(SabScriptsFolder)
  220. print "Loading SickBeard 'autoProcessTV' module"
  221. import autoProcessTV
  222. print 100*'-'
  223. print ""
  224. else:
  225. print 100*'#'
  226. print "Unable to find SickBeard autoProcessTV.py script in folder:"
  227. print SickBeardScript
  228. print 100*'#'
  229. sys.exit(1)
  230. # Exit if the file doesn't exist
  231. else:
  232. print 100*'#'
  233. print "Unable to find SABnzbd config.ini file in this folder:"
  234. print os.path.join(currentFolder, confFile)
  235. print 100*'#'
  236. sys.exit(1)
  237. # Change current directory to SABnzbd argument 1
  238. os.chdir(sys.argv[1])
  239. # display directory of the SABnzbd job
  240. currentFolder = os.getcwd()
  241. print "Current folder is " + currentFolder
  242. # rename SABnzbd job directory (coming from SABnzbd: never in CP850 format)
  243. print "Renaming destination folder to UTF-8 format..."
  244. renameFunc('', currentFolder)
  245. currentFolder = os.getcwd()
  246. print "Destination folder renamed !"
  247. print ""
  248. # Unpack 7z file(s)
  249. if (Unpack7z == True):
  250. print 100*'-'
  251. unpack7zFunc(currentFolder)
  252. print 100*'-'
  253. print ""
  254. # process each sub-folders starting from the deepest level
  255. print 100*'-'
  256. print "Renaming folders to UTF-8 format..."
  257. for dirname, dirnames, filenames in os.walk('.', topdown=False):
  258. for subdirname in dirnames:
  259. renameFunc(dirname, subdirname)
  260. print "Folder renaming Done !"
  261. print 100*'-'
  262. print ""
  263. # process each file recursively
  264. print 100*'-'
  265. print "Renaming files to UTF-8 format..."
  266. for dirname, dirnames, filenames in os.walk('.'):
  267. for filename in filenames:
  268. renameFunc(dirname, filename)
  269. print "Files renaming Done !"
  270. print 100*'-'
  271. print ""
  272. # Move current folder to an another destination if the option has been configured
  273. if (MoveToThisFolder != ''):
  274. print 100*'-'
  275. print "Moving folder:"
  276. print os.getcwd()
  277. print "to:"
  278. print MoveToThisFolder
  279. # Check if destination folder does exist and can be written
  280. # If destination doesn't exist, it will be created
  281. if (os.access(MoveToThisFolder, os.F_OK) == False):
  282. os.makedirs(MoveToThisFolder)
  283. os.chmod(MoveToThisFolder, 0777)
  284. # Check write access
  285. if (os.access(MoveToThisFolder, os.W_OK) == False):
  286. print 100*'#'
  287. print "File(s)/Folder(s) can not be move in %s" %(MoveToThisFolder)
  288. print "Please, check Unix permissions"
  289. print 100*'#'
  290. sys.exit(1)
  291. # If MoveMergeSubFolder is True, then move all file(s)/folder(s) to destination
  292. # then remove source folder
  293. destFolder = os.path.join(MoveToThisFolder, os.path.split(os.getcwd())[-1])
  294. if (MoveMergeSubFolder):
  295. print " Info: Merge option is ON (incremental copy)"
  296. try:
  297. synoCopy_cmd = ['/bin/cp', '-Rpf', os.path.abspath(currentFolder), MoveToThisFolder]
  298. p = subprocess.Popen(synoCopy_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  299. out, err = p.communicate()
  300. if (str(err) != ''):
  301. print("Copy failed: " + str(err))
  302. sys.exit(1)
  303. except OSError, e:
  304. print("Unable to run cp: " + str(e))
  305. sys.exit(1)
  306. os.chdir(MoveToThisFolder)
  307. shutil.rmtree(currentFolder)
  308. # If MoveMergeSubFolder is False, remove folder with same (if exists)
  309. # then move all file(s)/folder(s) to destination and remove source folder
  310. else:
  311. print " Info: Merge option is OFF (existing data will be deleted and replaced)"
  312. # Remove if destination already exist
  313. if os.path.exists(destFolder):
  314. shutil.rmtree(destFolder)
  315. shutil.move(currentFolder, MoveToThisFolder)
  316. os.chdir(MoveToThisFolder)
  317. # Update currentFolder variable
  318. os.chdir(destFolder)
  319. currentFolder = os.getcwd()
  320. print 100*'-'
  321. print ""
  322. # Add multimedia files in the Syno DLNA if the option has been enabled
  323. if (IndexInSynoDLNA == True) & (SickBeardPostProcessing == False):
  324. print 100*'-'
  325. addToSynoIndex(currentFolder)
  326. print ""
  327. print 100*'-'
  328. # Send to SickBeard for post-processing
  329. elif (IndexInSynoDLNA == False) & (SickBeardPostProcessing == True):
  330. print 100*'-'
  331. print "Launching SickBeard post-processing..."
  332. autoProcessTV.processEpisode(currentFolder)
  333. print "SickBeard post-processing done!"
  334. print ""
  335. print 100*'-'
  336. # Display error message + advise if both options are enabled
  337. elif (IndexInSynoDLNA == True) & (SickBeardPostProcessing == True):
  338. print 100*'#'
  339. print "IndexInSynoDLNA and SickBeardPostProcessing options are exclusive"
  340. print "Please check your configuration"
  341. print ""
  342. print "If you want to have both options enables at the same time, please processed as follow:"
  343. print " 1- Enable only SickBeardPostProcessing option"
  344. print " 2- In SickBeard GUI -> Config -> Notifications -> Enable 'Synology Indexer'"
  345. print 100*'#'
  346. sys.exit(1)
  347. print ""
  348. print "Character encoding translation done!"