2019-10-10 21:17:11 +02:00
#!/usr/bin/python3
2019-10-08 21:20:27 +02:00
import shutil , zipfile , sys , getopt , os , json , platform , csv , posixpath , subprocess
2019-10-12 07:30:58 +02:00
from PIL import Image
2019-10-08 21:20:27 +02:00
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
2019-10-10 21:17:11 +02:00
LOG_FILE = " converter.log "
WARN_FILE = " converter.warn.log "
2019-10-10 23:40:59 +02:00
warnLogFile = " "
logFile = " "
# Set to true to enable logging of warnings
debug = False
if debug :
warnLogFile = open ( WARN_FILE , ' w ' )
logFile = open ( LOG_FILE , ' w ' )
2019-10-08 21:20:27 +02:00
def warn ( msg ) :
2019-10-10 23:40:59 +02:00
global warning_count , debug
2019-10-08 21:20:27 +02:00
print ( " WARNING: " + msg )
warning_count + = 1
2019-10-10 23:40:59 +02:00
if debug :
warnLogFile . write ( " WARNING: " + msg + " \n " )
def log ( msg ) :
if debug :
logFile . write ( msg + " \n " )
2019-10-08 21:20:27 +02:00
2019-10-12 07:30:58 +02:00
# This function is called to check if an image is animated. Returns True if it is animated, false if it is a static image
def is_animated ( imagePath ) :
global TEXTURE_SIZE
im = Image . open ( imagePath )
width , height = im . size
if width == height :
return False
elif height % width == 0 : # Height is an even factor of width, so it's probably animated frames of an item/block, so make sure we crop it.
return True
else : # Height is not an even factor of the width, so it probably isn't animated and is a mob texture
return False
2019-10-08 21:20:27 +02:00
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 :
2019-10-10 23:40:59 +02:00
subprocess . run ( [ magick_prefix , CONVERT , src , " -crop " , str ( xl ) + " x " + str ( yl ) + " + " + str ( xs ) + " + " + str ( ys ) , str ( dst ) ] , check = True , shell = True )
2019-10-08 21:20:27 +02:00
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 :
2019-10-10 23:40:59 +02:00
subprocess . run ( [ magick_prefix , CONVERT , colorMap , " -crop " , " 1x1+ " + selectedColormapPixel , " -depth " , " 8 " , " -resize " , str ( textureSize ) + " x " + str ( textureSize ) , " tmpABCDEFGH1234567File.png " ] , check = True , shell = True )
2019-10-08 21:20:27 +02:00
except subprocess . CalledProcessError as err :
warn ( " Convert of greyscale of source file " + str ( src ) + " failed! Skipping... " )
print ( err )
try :
2019-10-10 23:40:59 +02:00
subprocess . run ( [ magick_prefix , COMPOSITE , " -compose " , " Multiply " , " tmpABCDEFGH1234567File.png " , src , dst ] , check = True , shell = True )
2019-10-08 21:20:27 +02:00
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 :
2019-10-10 23:40:59 +02:00
subprocess . run ( [ magick_prefix , COMPOSITE , " -compose " , " Dst_In " , src , " tmpBCDEFGHI123456.png " , " -alpha " , " Set " , str ( dst ) ] , check = True , shell = True )
2019-10-08 21:20:27 +02:00
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 = " "
2019-10-10 23:40:59 +02:00
log ( " No prefix for magick " )
2019-10-08 21:20:27 +02:00
return True
else :
2019-10-10 23:40:59 +02:00
log ( " Using magick prefix for magick " )
2019-10-08 21:20:27 +02:00
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: " )
2019-10-10 23:40:59 +02:00
print ( " python3 convert-mc-resource-pack.py [OPTIONS] INPUT-RESOURCE-PACK.zip [OUTPUT-NAME] " )
2019-10-08 21:20:27 +02:00
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. " )
2019-10-10 23:40:59 +02:00
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. " )
2019-10-08 21:20:27 +02:00
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. " )
2019-10-10 23:40:59 +02:00
print ( " -d --debug Enables debug mode and logging to log and warning files. " )
2019-10-08 21:20:27 +02:00
#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 ( ) :
2019-10-10 23:40:59 +02:00
global input_zip , output_directory , logFile , warnLogFile , debug
2019-10-08 21:20:27 +02:00
try :
2019-10-10 23:40:59 +02:00
opts , args = getopt . gnu_getopt ( sys . argv [ 1 : ] , " hvd " , [ " help " , " version " , " debug " ] )
2019-10-08 21:20:27 +02:00
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 )
2019-10-10 23:40:59 +02:00
elif o in ( " -d " , " --debug " ) :
debug = True
warnLogFile = open ( WARN_FILE , ' w ' )
logFile = open ( LOG_FILE , ' w ' )
2019-10-08 21:20:27 +02:00
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... " )
2019-10-10 23:40:59 +02:00
log ( " Found MCMETA file " )
2019-10-08 21:20:27 +02:00
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 ) )
2019-10-10 23:40:59 +02:00
log ( " Resource pack is in pack format " + str ( pack_format ) )
2019-10-08 21:20:27 +02:00
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 )
2019-10-10 23:40:59 +02:00
log ( " Created output directory " )
2019-10-08 21:20:27 +02:00
#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 ( ) :
2019-10-12 07:30:58 +02:00
global input_directory , pack_format , output_directory , generating_for_game , TEXTURE_SIZE
2019-10-08 21:20:27 +02:00
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 " :
2019-10-10 23:40:59 +02:00
log ( " Source file " + source_file + " was blacklisted, skipping... " )
2019-10-08 21:20:27 +02:00
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
2019-10-12 07:30:58 +02:00
#Ensure that item/block is not animated, if it is animated, crop it.
if is_animated ( source ) :
warn ( " Source file " + source_file + " seems to be animated, cropping a single tile... " )
crop_image ( 0 , 0 , TEXTURE_SIZE , TEXTURE_SIZE , 0 , 0 , source , destination )
else :
shutil . copyfile ( source , destination )
2019-10-08 21:20:27 +02:00
else :
#Use Image Magick to crop and then rename
2019-10-10 23:40:59 +02:00
log ( " Cropping source file " + source_file )
2019-10-08 21:20:27 +02:00
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 )
2019-10-10 23:40:59 +02:00
log ( " Colorizing greyscale source file " + source_file )
2019-10-08 21:20:27 +02:00
# 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
2019-10-10 23:40:59 +02:00
log ( " Converting block break animation... " )
2019-10-08 21:20:27 +02:00
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 :
2019-10-10 23:40:59 +02:00
subprocess . run ( [ magick_prefix , MONTAGE , " -tile " , " 1x " + str ( i ) , " -geometry " , " +0+0 " , " -background " , " none " , str ( source ) , str ( destination ) , destination ] , check = True , shell = True )
2019-10-08 21:20:27 +02:00
except subprocess . CalledProcessError as err :
warn ( " Montage for crackig animation failed! Skipping... " )
print ( err )
return False
try :
2019-10-10 23:40:59 +02:00
subprocess . run ( [ magick_prefix , CONVERT , destination , " -alpha " , " on " , " -background " , " none " , " -channel " , " A " , " -evaluate " , " Min " , " 50 % " , str ( destination ) ] , check = True , shell = True )
2019-10-08 21:20:27 +02:00
except subprocess . CalledProcessError as err :
warn ( " Convert for crackig animation failed! Skipping... " )
print ( err )
return False
i + = 1
try :
2019-10-10 23:40:59 +02:00
subprocess . run ( [ magick_prefix , CONVERT , destination , " -rotate " , " 180 " , destination ] , check = True , shell = True )
2019-10-08 21:20:27 +02:00
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 ( )
2019-10-10 23:40:59 +02:00
log ( " Conversion Finished. " )
if debug :
warnLogFile . close ( )
logFile . close ( )
2019-10-08 21:20:27 +02:00
print ( " Finished with " + str ( warning_count ) + " warnings. " )