Advertisement
Guest User

Untitled

a guest
Sep 17th, 2017
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.20 KB | None | 0 0
  1. #!/usr/bin/python
  2. """
  3. Usage:
  4. ./convert.py source_folder destination_folder
  5.  
  6. Depends:
  7. apt install ffmpeg
  8. pip install subliminal
  9. """
  10.  
  11. import filecmp
  12. import os
  13. import shlex
  14. import shutil
  15. import subprocess
  16. import sys
  17.  
  18. OPENSUBTITLES_USER = "{{ opensubtitles_user }}"
  19. OPENSUBTITLES_PASS = "{{ opensubtitles_pass }}"
  20. TMP_FOLDER = "/tmp"
  21. EXT_SRT = ".srt"
  22. EXT_MKV = ".mkv"
  23. EXT_VIDEOS = [".avi", ".m4v", ".mp4", ".mkv"]
  24.  
  25.  
  26. def download_subtitles(src_path):
  27. subprocess.check_call([
  28. "subliminal",
  29. "--opensubtitles", OPENSUBTITLES_USER, OPENSUBTITLES_PASS,
  30. "download",
  31. "--language", "en", # "--language", "fr",
  32. "--age", "2w",
  33. "--min-score", "42",
  34. src_path
  35. ])
  36.  
  37.  
  38. def make_ffprobe_args(src_path):
  39. return [
  40. "ffprobe",
  41. "-i", src_path,
  42. "-show_entries", "format=duration",
  43. "-v", "quiet",
  44. "-of", "csv=p=0"
  45. ]
  46.  
  47.  
  48. def make_ffmpeg_args(src_path, dst_path):
  49. return [
  50. "ffmpeg",
  51. "-y", # overwrite if target exists
  52. "-loglevel", "warning", # don't spam
  53. "-stats", # display stats when processing
  54. "-i", src_path,
  55. "-map_chapters", "-1", # remove DVD chapter infos (buggy)
  56. "-movflags", "+faststart", # put metadata at the beginning of the file
  57. "-flags", "+global_header", # (seems recommended)
  58. "-map", "0", # convert all streams
  59. "-dn", # except data streams (unsupported by mkv)
  60. "-c", "copy", # by default, just copy
  61. "-c:s", "srt", # for subtitles, convert to SRT
  62. "-c:a", "aac", # for audio, convert to AAC
  63. "-c:v:0", "libx264", # for the first video stream, use x264
  64. "-maxrate", "3M", # no more than 3Mbps
  65. "-bufsize", "3M", # same lookahead
  66. "-profile:v:0", "main", # be compatible with...
  67. "-level", "4.0", # ... most devices
  68. "-tune", "film", # tweak for movies
  69. "-threads", "3", # don't use all the CPU
  70. dst_path
  71. ]
  72.  
  73.  
  74. class Context:
  75. def __init__(self):
  76. # not done yet
  77. self.actions = 1
  78.  
  79. def reset(self):
  80. self.actions = 0
  81.  
  82. def did_something(self):
  83. self.actions += 1
  84.  
  85. def done(self):
  86. return self.actions == 0
  87.  
  88.  
  89. def check_conversion(src_path, dst_path):
  90. # Newer?
  91. if os.path.getmtime(dst_path) < os.path.getmtime(src_path):
  92. print "check (newer)", dst_path
  93. return False
  94.  
  95. # Incomplete?
  96. try:
  97. src_output = subprocess.check_output(make_ffprobe_args(src_path))
  98. dst_output = subprocess.check_output(make_ffprobe_args(dst_path))
  99. except subprocess.CalledProcessError:
  100. print "check (fail)", dst_path
  101. return False
  102. src_secs = float(src_output)
  103. dst_secs = float(dst_output)
  104. # Allow two seconds difference
  105. if abs(src_secs - dst_secs) > 2:
  106. print "check (length)", dst_path
  107. return False
  108. return True
  109.  
  110.  
  111. def sync_convert(context, src, dst, item, filename, dst_files):
  112. src_path = os.path.join(src, item)
  113. converted_filename = filename + EXT_MKV
  114. tmp_converted_path = os.path.join(TMP_FOLDER, converted_filename)
  115. dst_path = os.path.join(dst, converted_filename)
  116. if converted_filename in dst_files:
  117. dst_files.remove(converted_filename)
  118. if check_conversion(src_path, dst_path):
  119. return
  120. print "convert", src_path
  121. subprocess.check_call(make_ffmpeg_args(src_path, tmp_converted_path))
  122. shutil.move(tmp_converted_path, dst_path)
  123. context.did_something()
  124.  
  125.  
  126. def sync_copy(context, src, dst, item, dst_files):
  127. src_path = os.path.join(src, item)
  128. dst_path = os.path.join(dst, item)
  129. if item in dst_files:
  130. dst_files.remove(item)
  131. if filecmp.cmp(src_path, dst_path):
  132. return
  133. print "copy", src_path
  134. shutil.copyfile(src_path, dst_path)
  135. context.did_something()
  136.  
  137.  
  138. def sync_files(context, src, dst, src_files, dst_files):
  139. for item in src_files:
  140. filename, extension = os.path.splitext(item)
  141. extension = extension.lower()
  142. if extension == EXT_SRT:
  143. sync_copy(context, src, dst, item, dst_files)
  144. elif extension in EXT_VIDEOS:
  145. sync_convert(context, src, dst, item, filename, dst_files)
  146. else:
  147. print "unknown file", os.path.join(src, item)
  148.  
  149. for item in dst_files:
  150. path = os.path.join(dst, item)
  151. print "remove", path
  152. os.remove(path)
  153. context.did_something()
  154.  
  155.  
  156. def sync_folders(context, dst, src_folders, dst_folders):
  157. src_set = set(src_folders)
  158. dst_set = set(dst_folders)
  159.  
  160. missing = src_set - dst_set
  161. for folder in missing:
  162. path = os.path.join(dst, folder)
  163. print "create folder", path
  164. os.mkdir(path, 0755)
  165. context.did_something()
  166.  
  167. extras = dst_set - src_set
  168. for folder in extras:
  169. path = os.path.join(dst, folder)
  170. print "remove folder", path
  171. shutil.rmtree(path)
  172. context.did_something()
  173.  
  174.  
  175. def convert_folder(context, src, dst):
  176. # Fetch entries
  177. (_, src_folders, src_files) = os.walk(src).next()
  178. (_, dst_folders, dst_files) = os.walk(dst).next()
  179. src_folders.sort()
  180. dst_folders.sort()
  181. src_files.sort()
  182. dst_files.sort()
  183.  
  184. # Add/remove folders
  185. sync_folders(context, dst, src_folders, dst_folders)
  186.  
  187. # Add/remove files
  188. sync_files(context, src, dst, src_files, dst_files)
  189.  
  190. # Recurse
  191. for folder in src_folders:
  192. convert_folder(context,
  193. os.path.join(src, folder),
  194. os.path.join(dst, folder))
  195.  
  196.  
  197. def already_running():
  198. cmd = shlex.split("pgrep -u {} -f {}".format(os.getuid(), __file__))
  199. pids = subprocess.check_output(cmd).strip().split('\n')
  200. return len(pids) > 1
  201.  
  202.  
  203. def main(argv):
  204. if len(argv) != 3:
  205. print "usage:", argv[0], "src", "dst"
  206. sys.exit(-1)
  207.  
  208. if already_running():
  209. print "already running, exiting"
  210. sys.exit(-1)
  211.  
  212. context = Context()
  213. while not context.done():
  214. context.reset()
  215. download_subtitles(argv[1])
  216. convert_folder(context, argv[1], argv[2])
  217.  
  218.  
  219. if __name__ == "__main__":
  220. main(sys.argv)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement