243 lines
9.4 KiB
Python
Executable File
243 lines
9.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
'''
|
|
MediaCurator is a Python command line tool to manage a media database.
|
|
* List all the video's and their codecs with or without filters
|
|
* Batch recode videos to more modern codecs (x265 / AV1) based on filters: extentions, codecs ...
|
|
ex:
|
|
./converter.py list -in:any -filters:old -dir:/mnt/media/ >> ../medlist.txt
|
|
./converter.py convert -del -in:any -filters:mpeg4 -out:x265,mkv -dir:"/mnt/media/Movies/"
|
|
./converter.py convert -del -verbose -in:avi,mpg -dir:/mnt/media/
|
|
|
|
'''
|
|
|
|
import sys
|
|
import os
|
|
import subprocess
|
|
import distro
|
|
from pathlib import Path
|
|
from pprint import pprint
|
|
from hurry.filesize import size
|
|
|
|
|
|
def main():
|
|
ffmpeg_version = detect_ffmpeg()
|
|
if not ffmpeg_version:
|
|
print(f"{bcolors.FAIL}No ffmpeg version detected{bcolors.ENDC}")
|
|
exit()
|
|
print(f"{bcolors.OKBLUE}ffmpeg detected: {ffmpeg_version}{bcolors.ENDC}")
|
|
|
|
if len(sys.argv) >= 2:
|
|
# Get command parameters
|
|
directories = []
|
|
inputs = []
|
|
filters = []
|
|
outputs = []
|
|
for arg in sys.argv:
|
|
if "-in:" in arg:
|
|
inputs += arg[4:].split(",")
|
|
elif "-filters:" in arg:
|
|
filters += arg[9:].split(",")
|
|
elif "-out:" in arg:
|
|
outputs += arg[5:].split(",")
|
|
elif "-dir:" in arg:
|
|
directories += arg[5:].split(",")
|
|
|
|
|
|
if sys.argv[1] == "list":
|
|
if any("-file" in argv for argv in sys.argv):
|
|
pass
|
|
elif any("-dir" in argv for argv in sys.argv):
|
|
videolist = []
|
|
#directory = sys.argv[sys.argv.index("-dir") + 1]
|
|
for directory in directories:
|
|
videolist += get_videolist(directory, inputs, filters)
|
|
for video in videolist:
|
|
print(f"{get_codec(video)} - {video}")
|
|
else:
|
|
print(f"{bcolors.FAIL}Missing directory: {bcolors.ENDC}")
|
|
elif sys.argv[1] == "test":
|
|
if any("-file" in argv for argv in sys.argv):
|
|
pass
|
|
elif any("-dir" in argv for argv in sys.argv):
|
|
print(f"directories = {directories}, inputs = {inputs}, filters = {filters}, outputs = {outputs}")
|
|
exit()
|
|
else:
|
|
print("{bcolors.FAIL}Missing directory: {bcolors.ENDC}")
|
|
|
|
|
|
elif sys.argv[1] == "convert":
|
|
if "av1" in outputs:
|
|
codec = "av1"
|
|
else:
|
|
codec = "x265"
|
|
if any("-file" in argv for argv in sys.argv):
|
|
video = sys.argv[sys.argv.index("-file") + 1]
|
|
folder = str(video)[:str(video).rindex("/") + 1]
|
|
oldfilename = str(video)[str(video).rindex("/") + 1:]
|
|
|
|
# Setting new filename
|
|
if "mp4" in outputs:
|
|
newfilename = oldfilename[:-4] + ".mp4"
|
|
else:
|
|
newfilename = oldfilename[:-4] + ".mkv"
|
|
|
|
# Modding the filename if same as original
|
|
if oldfilename == newfilename:
|
|
newfilename = oldfilename[:-4] + "[HEVC]"
|
|
|
|
|
|
|
|
print(f"{bcolors.OKCYAN}*********** converting {oldfilename} to {newfilename} ({codec}) ***********{bcolors.ENDC}")
|
|
try:
|
|
if convert(folder + oldfilename, folder + newfilename, codec):
|
|
subprocess.call(['chmod', '777', folder + newfilename])
|
|
if "-del" in sys.argv:
|
|
delete(folder + oldfilename)
|
|
except:
|
|
delete(folder + newfilename)
|
|
return False
|
|
elif any("-dir" in argv for argv in sys.argv):
|
|
videolist = []
|
|
for directory in directories:
|
|
videolist += get_videolist(directory, inputs, filters)
|
|
counter = 0
|
|
for video in videolist:
|
|
folder = str(video)[:str(video).rindex("/") + 1]
|
|
oldfilename = str(video)[str(video).rindex("/") + 1:]
|
|
newfilename = oldfilename[:-4] + ".mkv"
|
|
if oldfilename == newfilename:
|
|
newfilename = oldfilename[:-4] + "[HEVC]"
|
|
|
|
counter += 1
|
|
print(f"{bcolors.OKCYAN}*********** convert {counter} of {len(videolist)} ***********{bcolors.ENDC}")
|
|
try:
|
|
if convert(folder + oldfilename, folder + newfilename, codec):
|
|
if "-del" in sys.argv:
|
|
delete(folder + oldfilename)
|
|
except:
|
|
delete(folder + newfilename)
|
|
return False
|
|
|
|
def get_videolist(parentdir, inputs = ["any"], filters = []):
|
|
print(f"{bcolors.OKGREEN}Scanning files in {parentdir} for videos{bcolors.ENDC}")
|
|
videolist = []
|
|
|
|
path = Path(parentdir)
|
|
if "wmv" in inputs or "any" in inputs or len(inputs) < 1:
|
|
videolist += list(path.rglob("*.[wW][mM][vV]"))
|
|
if "avi" in inputs or "any" in inputs or len(inputs) < 1:
|
|
videolist += list(path.rglob("*.[aA][vV][iI]"))
|
|
if "mkv" in inputs or "any" in inputs or len(inputs) < 1:
|
|
videolist += list(path.rglob("*.[mM][kK][vV]"))
|
|
if "mp4" in inputs or "any" in inputs or len(inputs) < 1:
|
|
videolist += list(path.rglob("*.[mM][pP]4"))
|
|
if "m4v" in inputs or "any" in inputs or len(inputs) < 1:
|
|
videolist += list(path.rglob("*.[mM]4[vV]"))
|
|
if "flv" in inputs or "any" in inputs or len(inputs) < 1:
|
|
videolist += list(path.rglob("*.[fF][lL][vV]"))
|
|
if "mpg" in inputs or "any" in inputs or len(inputs) < 1:
|
|
videolist += list(path.rglob("*.[mM][pP][gG]"))
|
|
|
|
|
|
# Remove folders
|
|
videolist_tmp = videolist
|
|
videolist = [video for video in videolist_tmp if video.is_file()]
|
|
|
|
# Filter the list for specifi codecs
|
|
videolist_tmp = videolist
|
|
print(f"{bcolors.OKGREEN}Filtering {len(videolist)} videos for the requested parameters{bcolors.ENDC}")
|
|
if len(filters) > 0:
|
|
videolist = []
|
|
|
|
if "old" in filters:
|
|
videolist += [video for video in videolist_tmp if get_codec(video) not in ["hevc", "av1"]]
|
|
|
|
if "mpeg4" in filters or "mpeg" in filters:
|
|
videolist += [video for video in videolist_tmp if get_codec(video) in ["mpeg4", "msmpeg4v3"]]
|
|
|
|
if "mpeg" in filters:
|
|
videolist += [video for video in videolist_tmp if get_codec(video) in ["mpeg1video"]]
|
|
|
|
if "wmv3" in filters or "wmv" in filters:
|
|
videolist += [video for video in videolist_tmp if get_codec(video) in ["wmv3"]]
|
|
|
|
if "x264" in filters:
|
|
videolist += [video for video in videolist_tmp if get_codec(video) in ["x264"]]
|
|
|
|
print(f"{bcolors.OKGREEN}Found {len(videolist)} videos for the requested parameters{bcolors.ENDC}")
|
|
return videolist
|
|
|
|
|
|
def get_codec(filename):
|
|
try:
|
|
args = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=codec_name", "-of", "default=noprint_wrappers=1:nokey=1", str(filename)]
|
|
output = subprocess.check_output(args, stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError:
|
|
print(f"{bcolors.FAIL}There seams to be an error with {filename}{bcolors.ENDC}")
|
|
return False
|
|
return output.decode().strip()
|
|
|
|
def convert(oldfilename, newfilename, codec = "x265"):
|
|
oldsize = size(Path(oldfilename).stat().st_size)
|
|
print(f"{bcolors.OKGREEN}Starting conversion of {oldfilename}({oldsize}) from {get_codec(oldfilename)} to {codec}...{bcolors.ENDC}")
|
|
|
|
# Preparing ffmpeg command and input file
|
|
args = ['ffmpeg', '-i', oldfilename]
|
|
|
|
# conversion options
|
|
if codec == "av1":
|
|
args += ['-c:v', 'libaom-av1', '-strict', 'experimental']
|
|
else:
|
|
args += ['-c:v', 'libx265']
|
|
args += ['-max_muxing_queue_size', '1000']
|
|
|
|
# conversion output
|
|
args += [newfilename]
|
|
|
|
#args = ['ffmpeg', '-i', oldfilename, newfilename]
|
|
try:
|
|
if "-verbose" in sys.argv:
|
|
subprocess.call(args)
|
|
else:
|
|
txt = subprocess.check_output(args, stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"{bcolors.FAIL}Conversion failed {e}{bcolors.ENDC}")
|
|
return False
|
|
else:
|
|
newsize = size(Path(newfilename).stat().st_size)
|
|
oldfilename = str(oldfilename)[str(oldfilename).rindex("/") + 1:]
|
|
newfilename = str(newfilename)[str(newfilename).rindex("/") + 1:]
|
|
print(f"{bcolors.OKGREEN}Converted {oldfilename}({oldsize}) to {newfilename}({newsize}) successfully{bcolors.ENDC}")
|
|
return True
|
|
|
|
def delete(filename):
|
|
try:
|
|
os.remove(filename)
|
|
except OSError:
|
|
print(f"{bcolors.FAIL}Error deleting {filename}{bcolors.ENDC}")
|
|
return False
|
|
|
|
print(f"{bcolors.OKGREEN}Deleted {filename}{bcolors.ENDC}")
|
|
return True
|
|
|
|
def detect_ffmpeg():
|
|
try:
|
|
txt = subprocess.check_output(['ffmpeg', '-version'], stderr=subprocess.STDOUT).decode()
|
|
return txt.partition('\n')[0]
|
|
except:
|
|
return False
|
|
|
|
class bcolors:
|
|
HEADER = '\033[95m'
|
|
OKBLUE = '\033[94m'
|
|
OKCYAN = '\033[96m'
|
|
OKGREEN = '\033[92m'
|
|
WARNING = '\033[93m'
|
|
FAIL = '\033[91m'
|
|
ENDC = '\033[0m'
|
|
BOLD = '\033[1m'
|
|
UNDERLINE = '\033[4m'
|
|
|
|
if __name__ == '__main__':
|
|
main()
|