XenoTheStrange

FFMPEG Convert [codecs] to [codec] maintaining folder structure

Jul 9th, 2023 (edited)
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.44 KB | Source Code | 0 0
  1. #!/usr/bin/python3
  2. """This will take a folder and ffmpeg copy them to whatever directory as whatever extension, maintaining the folder heiarchy. Choose the same input and output folder to have converted files be in the same spot as old ones. You'll need to delete the source files manually if you want to do that because I didn't code that in."""
  3. """Symbols [? :] will be replaced or removed from file/folder names beacuase ntfs is dumb :/"""
  4.  
  5. """Usage: copy_them.py input_folder "input exts" output_ext output_folder [fcdrm]"""
  6. """Option f: overwrite files that exist.
  7. Option c: try to use ffmpeg -c copy. If this fails then it will create an empty file of the same name and not tell you there was a problem becuase I don't want to fix that right now.
  8. Option d: enable debug output"""
  9.  
  10. "I know this is a bit of a mess, but it's worked well enough for me lol. I might tinker with this more later when I feel like it."
  11. import os
  12. import sys
  13. import subprocess
  14. dbg = False
  15.  
  16. def help():
  17.     print("""Usage: script.py input-folder ["input exts"] output-ext output-folder [options])
  18.    Options:
  19.    -f force     Overwrite files if they exist. Inserts \"-y\" into ffmpeg command"
  20.    -c copy      Use ffmpeg -c copy option to copy streams instead of reencoding.
  21.                 This inserts "-c -copy -strict experimental" into ffmpeg command.
  22.                 "-strict experimental" enables experimental options like inserting opus audio into mp4 containers so that won't cause an error.")
  23.                 This will override the "standard" option.
  24.    -s standard  Adds the arguments "-c:v libx264 -profile:v high -vf format=yuv420p -c:a aac -ac 2" So that the media will play on my PS3 :D
  25.                 Do not use with -copy
  26.    -rm remove   Remove source files if conversion seems successful. Success is determined by wether the output file is empty (0 bytes in size) using the "du" command.
  27.    -d debug     Enable debug output, showing the command, stdout and stderr for each file operated on.
  28.    -b banner    Enable banner with ffmpeg configuration information. By default this information is omitted.
  29.    -l log       Generate log file in the current directory.
  30.    """)
  31.     exit()
  32.  
  33. def iferr(stderr):
  34.     if not stderr == "": return f"\n{stderr}"
  35.     return ""
  36.  
  37. def mkdir_recursive(path):
  38.     arr = path.split("/")
  39.     for i in range(len(arr)):
  40.         try:
  41.             tmp = "/".join(arr[0:i])
  42.             if tmp[-1] != "/": tmp = tmp + "/"
  43.             #print(tmp)
  44.             os.mkdir(tmp)
  45.         except:
  46.             continue
  47.  
  48. def main(in_dir, in_exts, out_ext, out_dir, options):
  49.     global dbg
  50.     in_exts = in_exts.split(" ")
  51.     out_dir = out_dir + "/" if out_dir[-1] != "/" else out_dir
  52.     in_dir = in_dir + "/" if in_dir[-1] != "/" else in_dir
  53.     out_ext = out_ext.split(".")[-1]
  54.     walk = list(os.walk(in_dir))
  55.     walk.sort()
  56.     base_dir = in_dir.split("/")[-1] if in_dir[-1] != "/" else in_dir.split("/")[-2]+"/"
  57.     current_file = 0
  58.     total = 0
  59.     existed = 0
  60.     more_args = ""
  61.     banner = " -hide_banner"
  62.     logging = False
  63.     remove = False
  64.     for i in options:
  65.         if "force" in i or "f" in i: more_args += "-y "
  66.         else: more_args += "-n "
  67.         if "debug" in i or "d" in i: dbg = True
  68.         if "delete" in i or "rm" in i: remove = True
  69.         if "copy" in i or "c" in i: more_args += "-c copy -strict experimental "
  70.         if "banner" in i or "b" in i: banner = ""
  71.         if "log" in i or "l" in i: logging = True
  72.         if "standard" in i or "s" in i: more_args += "-c:v libx264 -profile:v high -vf format=yuv420p -c:a aac -ac 2"
  73.         #if "st" in i: more_args += " -acodec mp3 -vcodec libx264"
  74.     if logging:log = open("log.txt","w")
  75.     for i in walk:
  76.         for j in i[2]:
  77.             if j.split(".")[-1].strip() in in_exts:
  78.                 total+=1
  79.     for folder in walk:
  80.         in_path = folder[0]+"/"
  81.         if in_path == ".//": in_path = "./"
  82.         #Replace a few symbols that commonly fuck with ntfs, cases I had to deal with. Same with filenames
  83.         rel_path = in_path.split(base_dir)[1].replace('?','').replace(':', ',')
  84.         if rel_path == "/":
  85.             rel_path = ""
  86.         #if not toggle_base_dir:
  87.         #full_path = f"{out_dir}{base_dir}{rel_path}"
  88.         #else:
  89.         full_path = f"{out_dir}{rel_path}"
  90.         files = [i for i in folder[2] if i.split(".")[-1].strip() in in_exts]
  91.         files.sort()
  92.         for file in files:
  93.             newfile = file.replace('?','').replace(':', ',')
  94.             #os.system("clear")
  95.             current_file += 1
  96.             print(f"""[{current_file}/{total}] Processing "{full_path}{file.replace(file.split(".")[-1], out_ext)}" """)
  97.             #try to create the folder heiarchy from the original output so ffmpeg will work
  98.             #ffmpeg really should be able to make whatever folders are in the output file path.
  99.             mkdir_recursive(full_path)
  100.             #REMOVE -c copy TO RE-ENCODE FOR SMALLER FILESIZE derp
  101.             command = f"""ffmpeg{banner} -nostats -i "{in_path}{file}" {more_args} "{full_path}{".".join(newfile.split(".")[0:-1])}.{out_ext}" """
  102.             proc = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  103.             stdout = proc.stderr.decode("utf-8")
  104.             stderr = proc.stderr.decode("utf-8")
  105.             if remove and not "cannot edit existing files in-place" in stdout:
  106.                 size = subprocess.check_output(["du", f"{in_path}{file}"]).decode("utf-8").split("\t")[0]
  107.                 if not size == 0: subprocess.run(["rm", f"{in_path}{file}"])
  108.             if stdout == "":print(stderr)
  109.             if "exist" in stdout and not "-y" in more_args:print("[Error] File already exists");existed+=1
  110.             if "cannot edit existing files in-place" in stdout: print("[Error] Source and output file are the same. Skipping...")
  111.             #else:print("Done")
  112.             if dbg:print(f"\n{command}\n\n{stdout}")
  113.             if logging:log.write(f"[{current_file}/{total}] {command}\n{stdout}{iferr(stderr)}\n")
  114.     if existed != 0:print(f"{existed} files already existed")
  115.     if logging: log.close()
  116.  
  117.  
  118. if __name__ == "__main__":
  119.     a = sys.argv
  120.     if "--help" in a[1] or ("-h" in a[1] and len(a[1]) == 2):
  121.         help()
  122.     try:
  123.         main(a[1], a[2], a[3], a[4], a[5:])
  124.     except IndexError:
  125.         print("Malformed arguments list. At least 5 arguments are required.\nFor help type ./script -h or ./script --help.")
  126.         print(a)
  127.  
Tags: python ffmpeg
Add Comment
Please, Sign In to add comment