373 lines
16 KiB
Python
Executable File
373 lines
16 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import shutil, zipfile, sys, getopt, os, json, platform, csv, posixpath, subprocess
|
|
|
|
pack_format=1
|
|
TEXTURE_SIZE = 64
|
|
#Turn this to true to have files inserted into the MCL2 mod directory to use as default textures
|
|
generating_for_game=False
|
|
|
|
#This is the path to the zipped source resource pack
|
|
input_zip=""
|
|
|
|
#This is the path to the ouput directory mcl2 pack
|
|
output_directory="CONVERTED_TEXTURE_PACK"
|
|
|
|
#This is the path to the temporary unzipped source resource pack
|
|
input_directory="UNZIPPED_TMP_FILE_ABCDEFGHJKILM"
|
|
|
|
#"Linux" is linux, "Windows" is windows, "Darwin" is MacOS/Apple
|
|
platform=platform.system()
|
|
|
|
#Image Magick command variables
|
|
magick_prefix = ""
|
|
CONVERT="convert"
|
|
COMPOSITE="composite"
|
|
MOGRIFY="mogrify"
|
|
DISPLAY="display"
|
|
ANIMATE="animate"
|
|
COMPARE="compare"
|
|
CONJURE="conjure"
|
|
IDENTIFY="identify"
|
|
IMPORT="import"
|
|
MONTAGE="montage"
|
|
STREAM="stream"
|
|
magick_version = 7
|
|
VERSION="1.0"
|
|
|
|
warning_count=0
|
|
LOG_FILE="converter.log"
|
|
WARN_FILE="converter.warn.log"
|
|
warnLogFile = ""
|
|
logFile = ""
|
|
|
|
# Set to true to enable logging of warnings
|
|
debug = False
|
|
if debug:
|
|
warnLogFile = open(WARN_FILE, 'w')
|
|
logFile = open(LOG_FILE, 'w')
|
|
|
|
def warn(msg):
|
|
global warning_count, debug
|
|
print("WARNING: " + msg)
|
|
warning_count += 1
|
|
if debug:
|
|
warnLogFile.write("WARNING: " + msg + "\n")
|
|
|
|
def log(msg):
|
|
if debug:
|
|
logFile.write(msg + "\n")
|
|
|
|
def crop_image(xs,ys,xl,yl,xt,yt,src,dst):
|
|
global magick_prefix, CONVERT, COMPOSITE, MOGRIFY, DISPLAY, ANIMATE, COMPARE, CONJURE, IDENTIFY, IMPORT, MONTAGE, STREAM
|
|
if magick_version == 7:
|
|
try:
|
|
subprocess.run([magick_prefix, CONVERT, src, "-crop", str(xl) + "x" + str(yl) + "+" + str(xs) + "+" + str(ys), str(dst)], check=True, shell=True)
|
|
except subprocess.CalledProcessError as err:
|
|
warn("Crop of source file " + str(src) + " failed! Skipping...")
|
|
print(err)
|
|
return False
|
|
return True
|
|
|
|
# Called to colorize a greyscale texture
|
|
def colorize_greyscale(src, colorMap, selectedColormapPixel, textureSize, dst):
|
|
global magick_prefix, CONVERT, COMPOSITE, MOGRIFY, DISPLAY, ANIMATE, COMPARE, CONJURE, IDENTIFY, IMPORT, MONTAGE, STREAM
|
|
if magick_version == 7:
|
|
try:
|
|
subprocess.run([magick_prefix, CONVERT, colorMap, "-crop", "1x1+"+selectedColormapPixel, "-depth", "8", "-resize", str(textureSize) + "x" + str(textureSize), "tmpABCDEFGH1234567File.png"], check=True, shell=True)
|
|
except subprocess.CalledProcessError as err:
|
|
warn("Convert of greyscale of source file " + str(src) + " failed! Skipping...")
|
|
print(err)
|
|
|
|
try:
|
|
subprocess.run([magick_prefix, COMPOSITE, "-compose", "Multiply", "tmpABCDEFGH1234567File.png", src, dst], check=True, shell=True)
|
|
except subprocess.CalledProcessError as err:
|
|
warn("Composite of greyscale of source file " + str(src) + " failed! Skipping...")
|
|
print(err)
|
|
return False
|
|
return True
|
|
|
|
# Called to colorize a greyscale texture and preserve alpha
|
|
def colorize_alpha(src, colorMap, selectedColormapPixel, textureSize, dst):
|
|
global magick_prefix, CONVERT, COMPOSITE, MOGRIFY, DISPLAY, ANIMATE, COMPARE, CONJURE, IDENTIFY, IMPORT, MONTAGE, STREAM
|
|
if not colorize_greyscale(src, colorMap, selectedColormapPixel, textureSize, "tmpBCDEFGHI123456.png"):
|
|
return False
|
|
if magick_version == 7:
|
|
try:
|
|
subprocess.run([magick_prefix, COMPOSITE, "-compose", "Dst_In", src, "tmpBCDEFGHI123456.png", "-alpha", "Set", str(dst)], check=True, shell=True)
|
|
except subprocess.CalledProcessError as err:
|
|
warn("Colorizing alpha of source file " + str(src) + " failed! Skipping...")
|
|
print(err)
|
|
return False
|
|
return True
|
|
|
|
#This function checks to ensure that Image Magick is installed, and if it is, detects if the programs are installed separately or as one
|
|
def check_magick():
|
|
global magick_prefix, CONVERT, COMPOSITE, MOGRIFY, DISPLAY, ANIMATE, COMPARE, CONJURE, IDENTIFY, IMPORT, MONTAGE, STREAM
|
|
if shutil.which("magick") == None:
|
|
if shutil.which("compose") == None:
|
|
return False
|
|
else:
|
|
magick_prefix = ""
|
|
log("No prefix for magick")
|
|
return True
|
|
else:
|
|
log("Using magick prefix for magick")
|
|
magick_prefix = "magick"
|
|
#CONVERT="magick convert"
|
|
#COMPOSITE="magick composite"
|
|
#MOGRIFY="magick mogrify"
|
|
#DISPLAY="magick display"
|
|
#ANIMATE="magick animate"
|
|
#COMPARE="magick compare"
|
|
#CONJURE="magick conjure"
|
|
#IDENTIFY="magick identify"
|
|
#IMPORT="magick import"
|
|
#MONTAGE="magick montage"
|
|
#STREAM="magick stream"
|
|
return True
|
|
|
|
#Prints usage text
|
|
def show_usage():
|
|
print("Texture Pack Converter")
|
|
print("Version: " + VERSION)
|
|
print("This script is designed to take a MC (1.14) resource pack and create a texture pack for use with MineClone2.")
|
|
print("DO NOT REDISTRIBUTE ANY RESULUTING TEXTUREPACKS! COPYRIGHT AND LICENSING RESTRICTIONS MOST LIKELY STILL APPLY SO DO NOT REDISTRIBUTE ANY RESULTING TEXTURE PACKS!")
|
|
print("")
|
|
print("Usage:")
|
|
print(" python3 convert-mc-resource-pack.py [OPTIONS] INPUT-RESOURCE-PACK.zip [OUTPUT-NAME]")
|
|
print("")
|
|
print(" INPUT-RESOURCE-PACK does not have to be in a .zip file. If it is unzipped already then that is ok. If it is zipped, this script will unzip it into this directory.")
|
|
print(" OUTPUT-NAME is optional, if no output name is given, the resulting MineClone2 texture pack will be written to OUTPUT-NAME, or set to the value of .pack.name in the pack.mcmeta file.")
|
|
print(" OPTIONS may be one of:")
|
|
print(" -h --help Display this usage text.")
|
|
print(" -v --version Display the version of this script and then exit.")
|
|
print(" -d --debug Enables debug mode and logging to log and warning files.")
|
|
|
|
#Prints version message
|
|
def show_version():
|
|
print("Cross Platform Minecraft Resource Pack Converter Version " + VERSION)
|
|
|
|
#Processes cmdln args, exits after printing usage or version text. Handles errors for too many or too few args.
|
|
def process_args():
|
|
global input_zip, output_directory, logFile, warnLogFile, debug
|
|
try:
|
|
opts, args = getopt.gnu_getopt(sys.argv[1:], "hvd", ["help", "version", "debug"])
|
|
except getopt.GetoptError as err:
|
|
print(err)
|
|
usage()
|
|
sys.exit(2)
|
|
for o, a in opts:
|
|
if o in ("-h", "--help"):
|
|
show_usage()
|
|
sys.exit(0)
|
|
elif o in ("-v", "--version"):
|
|
show_version()
|
|
sys.exit(0)
|
|
elif o in ("-d", "--debug"):
|
|
debug = True
|
|
warnLogFile = open(WARN_FILE, 'w')
|
|
logFile = open(LOG_FILE, 'w')
|
|
if len(args) < 1:
|
|
print("No input file specified! You must specify the path to the resource pack you want to convert! Stopping.")
|
|
sys.exit(-1)
|
|
else:
|
|
input_zip = args[0]
|
|
if len(args) > 1:
|
|
if len(args) == 2:
|
|
output_directory=args[1]
|
|
else:
|
|
print("Too many arguments specified! Please see --help for correct usage!")
|
|
sys.exit(-1)
|
|
else:
|
|
output_directory=os.path.join(os.path.dirname(input_zip), os.path.splitext(os.path.basename(input_zip))[0] + "_CONVERTED", "")
|
|
|
|
#Ensures that the input file is a ZIP, checks that it exists, and extracts it to the temporary input_directory
|
|
def extract_zip():
|
|
global input_zip, output_path, platform, input_directory
|
|
if not ".zip" == os.path.splitext(input_zip)[-1]:
|
|
print("Error! Selected input file is not a ZIP file!")
|
|
sys.exit(-1)
|
|
if not os.path.isfile(input_zip):
|
|
print("Error! Selected input file does not exist!")
|
|
sys.exit(-1)
|
|
print("Attempting to extract targeted ZIP file...")
|
|
input_directory = os.path.join(os.path.dirname(input_zip), os.path.splitext(os.path.basename(input_zip))[0] + "_UNZIPPED", "")
|
|
with zipfile.ZipFile(input_zip, 'r') as zip_ref:
|
|
zip_ref.extractall(input_directory)
|
|
|
|
#Ensures that the unzipped resource pack looks like a resource pack and extracts the version information from it
|
|
def check_structure():
|
|
global input_directory, platform, output_directory, pack_format
|
|
print("Ensuring extracted resource pack is sane...")
|
|
if not os.path.isdir(input_directory):
|
|
print("Extraction failed! Double check your file permissions!")
|
|
sys.exit(-1)
|
|
if not os.path.isdir(os.path.join(input_directory, "assets", "")):
|
|
print("Extracted ZIP does not contain an assets directory! Are you sure it's a resource pack?")
|
|
sys.exit(-1)
|
|
if os.path.isfile(os.path.join(input_directory, "pack.mcmeta")):
|
|
print("Found MCMETA file! Yay! Parsing information...")
|
|
log("Found MCMETA file")
|
|
with open(os.path.join(input_directory, "pack.mcmeta")) as mcmeta:
|
|
pack_info = json.load(mcmeta)
|
|
pack_format = pack_info["pack"]["pack_format"]
|
|
print("Resource pack is in pack format " + str(pack_format))
|
|
log("Resource pack is in pack format " + str(pack_format))
|
|
else:
|
|
warn("No MCMETA file found! Assuming old version 1 format...")
|
|
pack_format = 1
|
|
|
|
#Creates the output directory
|
|
def create_output_directory():
|
|
global output_directory, platform
|
|
if os.path.isdir(output_directory):
|
|
warn("Output directory " + output_directory + " already exists, overwriting!")
|
|
shutil.rmtree(output_directory)
|
|
os.mkdir(output_directory)
|
|
log("Created output directory")
|
|
|
|
#Ensures that we have access to the CSV reference files we use to rename files
|
|
def check_csv_files():
|
|
global pack_format
|
|
if not os.path.isdir("pack_formats"):
|
|
print("ERROR! FATAL! The pack_formats directory could not be found! This directory is critical for this program to work!")
|
|
sys.exit(-1)
|
|
if not os.path.isfile(os.path.join("pack_formats", str(pack_format) + ".csv")):
|
|
print("FATAL: The pack format CSV file in the pack_formats directory could not be found! Stopping!")
|
|
print("Pack format: " + str(pack_format))
|
|
sys.exit(-1)
|
|
|
|
#Deletes all temporary files and directories.
|
|
def clean_up():
|
|
global input_directory, output_directory
|
|
#shutil.rmtree(input_directory)
|
|
|
|
def crop_and_copy():
|
|
global input_directory, pack_format, output_directory, generating_for_game
|
|
with open(os.path.join("pack_formats", str(pack_format) + ".csv")) as formatCSV:
|
|
readCSV = csv.reader(formatCSV, delimiter=',')
|
|
for source_path, source_file, target_path, target_file, xs, ys, xl, yl, xt, yt, blacklisted in readCSV:
|
|
if blacklisted == "y":
|
|
log("Source file " + source_file + " was blacklisted, skipping...")
|
|
continue
|
|
if(generating_for_game):
|
|
destination = os.path.join(target_path[1:].replace("/",os.sep), target_file)
|
|
else:
|
|
destination = os.path.join(output_directory, target_file)
|
|
#Replace our posix file separators with whatever separators we are using in the os
|
|
#Also select [1:] because of the leading / that we have on the source_path
|
|
source = os.path.join(source_path[1:].replace("/",os.sep), source_file)
|
|
source = os.path.join(input_directory, source)
|
|
if not os.path.isfile(source):
|
|
warn("Source file " + str(source_file) + " not found! Skipping...")
|
|
continue
|
|
if xs == "":
|
|
#Don't have to crop, just copy and rename
|
|
shutil.copyfile(source, destination)
|
|
else:
|
|
#Use Image Magick to crop and then rename
|
|
log("Cropping source file " + source_file)
|
|
crop_image(xs,ys,xl,yl,xt,yt,source,destination)
|
|
|
|
def colorize_textures():
|
|
global input_directory, output_directory
|
|
with open(os.path.join("pack_formats", "colorize-" + str(pack_format) + ".csv")) as formatCSV:
|
|
readCSV = csv.reader(formatCSV, delimiter=',')
|
|
for source_path, source_file, colormap_file, colormap_point, target_path, target_file in readCSV:
|
|
colormap = os.path.join(input_directory, os.path.join("/assets/minecraft/textures/colormap/"[1:].replace("/",os.sep), colormap_file))
|
|
source = os.path.join(source_path[1:].replace("/",os.sep), source_file)
|
|
source = os.path.join(input_directory, source)
|
|
#determine where the resulting file will go
|
|
if(generating_for_game):
|
|
destination = os.path.join(target_path[1:].replace("/",os.sep), target_file)
|
|
else:
|
|
destination = os.path.join(output_directory, target_file)
|
|
|
|
log("Colorizing greyscale source file " + source_file)
|
|
|
|
# Check that our input files exist
|
|
if not os.path.isfile(colormap):
|
|
warn("Color map " + str(colormap_file) + " not found for source file " + source_file + "! Skipping...")
|
|
continue
|
|
|
|
if not os.path.isfile(colormap):
|
|
warn("Greyscale Source file " + source_file + " not found! Skipping...")
|
|
continue
|
|
|
|
colorize_alpha(source, colormap, colormap_point, TEXTURE_SIZE, destination)
|
|
|
|
def convert_block_break():
|
|
global input_directory, output_directory, magick_prefix, CONVERT, COMPOSITE, MOGRIFY, DISPLAY, ANIMATE, COMPARE, CONJURE, IDENTIFY, IMPORT, MONTAGE, STREAM
|
|
log("Converting block break animation...")
|
|
destination = os.path.join(output_directory, "crack_anylength.png")
|
|
if pack_format == 4:
|
|
base = "/assets/minecraft/textures/block/destroy_stage_"
|
|
else:
|
|
base = "/assets/minecraft/textures/blocks/destroy_stage_"
|
|
if not os.path.isfile(os.path.join(input_directory, base[1:].replace("/",os.sep)+"0.png")):
|
|
warn("No block breaking animation found, skipping...")
|
|
return False
|
|
else:
|
|
done = False
|
|
i=0
|
|
source = os.path.join(input_directory, base[1:].replace("/",os.sep)+str(i)+".png")
|
|
shutil.copy(source,destination)
|
|
i=1
|
|
while not done:
|
|
source = os.path.join(input_directory, base[1:].replace("/",os.sep)+str(i)+".png")
|
|
|
|
if not os.path.isfile(source):
|
|
done = True
|
|
break
|
|
if os.path.isfile("tmpABCD.png"):
|
|
os.remove("tmpABCD.png")
|
|
|
|
try:
|
|
subprocess.run([magick_prefix, MONTAGE, "-tile", "1x" + str(i), "-geometry", "+0+0", "-background", "none", str(source), str(destination), destination], check=True, shell=True)
|
|
except subprocess.CalledProcessError as err:
|
|
warn("Montage for crackig animation failed! Skipping...")
|
|
print(err)
|
|
return False
|
|
|
|
try:
|
|
subprocess.run([magick_prefix, CONVERT, destination, "-alpha", "on", "-background", "none", "-channel", "A", "-evaluate", "Min", "50%", str(destination)], check=True, shell=True)
|
|
except subprocess.CalledProcessError as err:
|
|
warn("Convert for crackig animation failed! Skipping...")
|
|
print(err)
|
|
return False
|
|
i+=1
|
|
try:
|
|
subprocess.run([magick_prefix, CONVERT, destination, "-rotate", "180", destination], check=True, shell=True)
|
|
except subprocess.CalledProcessError as err:
|
|
warn("Rotation for crackig animation failed! Skipping...")
|
|
print(err)
|
|
return False
|
|
return True
|
|
|
|
process_args()
|
|
check_magick()
|
|
if not generating_for_game:
|
|
create_output_directory()
|
|
#User can specify to input an already extracted ZIP file instead of extracting all over again
|
|
if not os.path.isdir(input_zip):
|
|
extract_zip()
|
|
else:
|
|
input_directory = input_zip
|
|
check_structure()
|
|
check_csv_files()
|
|
print("Copying files...")
|
|
crop_and_copy()
|
|
print("Colorizing grass and leaves...")
|
|
colorize_textures()
|
|
print("Converting block break animation...")
|
|
convert_block_break()
|
|
|
|
clean_up()
|
|
log("Conversion Finished.")
|
|
if debug:
|
|
warnLogFile.close()
|
|
logFile.close()
|
|
|
|
print("Finished with " + str(warning_count) + " warnings.")
|