Skip to content

Commit

Permalink
- improved unicode handling for TITLE_XX values and filenames
Browse files Browse the repository at this point in the history
- improved help texts
  • Loading branch information
n1ghty committed Mar 18, 2018
1 parent aefaa88 commit 7bf1024
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 113 deletions.
97 changes: 48 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,35 @@ and generates an excel sheet from the parsed infos.

### Usage
```
usage: pkg_list.py [-h] [-r] [-c COLUMN [COLUMN ...]] [-s SORT] [-d]
[-o OUTFILE]
pkg_path [pkg_path ...]
usage: pkg_list.py [-h] pkg_path [pkg_path ...] [-r] [-c COLUMN [COLUMN ...]] [-s SORT] [-d] [-o OUTFILE]
This tool parses all pkg files in the specified directory/directories recursively
and generates an excel sheet from the parsed infos.
Available values for the columns:
- Values from param.sfo like
TITLE, TITLE_ID, CONTENT_ID, VERSION, APP_VER, PARENTAL_LEVEL,
SYSTEM_VER, ...
- LANGUAGES
The list of title name languages, e.g. 'EN,FR,RU'
This does not always reflect supported languages. - VER
Equals VERSION for a game / an application and APP_VER(U) for an update
- REGION
The region of the pkg (CN, EU, US)
- SIZE
The filesize in a readable format, e.g. '1.1 GB'
- SYS_VER
The required system version number in a readable format, e.g. '2.70'
- SDK_VER
The used sdk version number in a readable format, e.g. '2.70'
- TITLE_XX
The title name in a specific language XX. If not available, the default
language is used.
Available language codes:
JA, EN, FR, ES, DE, IT, NL, PT, RU, KO, CH, ZH, FI, SV, DA,
NO, PL, BR, GB, TR, LA, AR, CA, CS, HU, EL, RO, TH, VI, IN
Raw values from param.sfo like
- TITLE, TITLE_ID, CONTENT_ID, VERSION, APP_VER, PARENTAL_LEVEL,
SYSTEM_VER, ...
Formatted values, especially for version information:
- LANGUAGES
The list of title name languages, e.g. 'EN,FR,RU'
This does not always reflect supported languages. - VER
Equals VERSION for a game / an application and APP_VER(U) for an update
- SYS_VER
The required system version number in a readable format, e.g. '2.70'
- SDK_VER
The used sdk version number in a readable format - if available - e.g. '2.70'
- REGION
The region of the pkg (CN, EU, US)
- SIZE
The filesize in a readable format, e.g. '1.1 GB'
- TITLE_XX
The title name in a specific language XX. If not available, the default
language is used.
Available language codes:
JA, EN, FR, ES, DE, IT, NL, PT, RU, KO, CH, ZH, FI, SV, DA,
NO, PL, BR, GB, TR, LA, AR, CA, CS, HU, EL, RO, TH, VI, IN
positional arguments:
pkg_path the path(s) to scan for pkg files
Expand All @@ -61,7 +60,7 @@ name format or a custom specified format.

### Usage
```
usage: pkg_rename.py [-h] [-t] [-c CUSTOM_FORMAT] [-n] [-d] [-r] pkg_path
usage: pkg_rename.py [-h] pkg_path [-t] [-c CUSTOM_FORMAT] [-n] [-d] [-r]
This tool renames PS4 pkg files to the sony format (default), a readable
name format or a custom specified format.
Expand All @@ -71,29 +70,29 @@ For the custom formatting, values can be replaced by surrounding them with
E.g. '%TITLE% (%TITLE_ID%)' will result in 'Game name (CUSA01234)'
Available values for formatting:
- Values from param.sfo like
TITLE, TITLE_ID, CONTENT_ID, VERSION, APP_VER, PARENTAL_LEVEL,
SYSTEM_VER, ...
- LANGUAGES
The list of title name languages, e.g. 'EN,FR,RU'
This does not always reflect supported languages. - VER
Equals VERSION for a game / an application and APP_VER(U) for an update
- REGION
The region of the pkg (CN, EU, US)
- SIZE
The filesize in a readable format, e.g. '1.1 GB'
- SYS_VER
The required system version number in a readable format, e.g. '2.70'
- SDK_VER
The used sdk version number in a readable format, e.g. '2.70'
- TITLE_XX
The title name in a specific language XX. If not available, the default
language is used.
Available language codes:
JA, EN, FR, ES, DE, IT, NL, PT, RU, KO, CH, ZH, FI, SV, DA,
NO, PL, BR, GB, TR, LA, AR, CA, CS, HU, EL, RO, TH, VI, IN
Raw values from param.sfo like
- TITLE, TITLE_ID, CONTENT_ID, VERSION, APP_VER, PARENTAL_LEVEL,
SYSTEM_VER, ...
Formatted values, especially for version information:
- LANGUAGES
The list of title name languages, e.g. 'EN,FR,RU'
This does not always reflect supported languages. - VER
Equals VERSION for a game / an application and APP_VER(U) for an update
- SYS_VER
The required system version number in a readable format, e.g. '2.70'
- SDK_VER
The used sdk version number in a readable format - if available - e.g. '2.70'
- REGION
The region of the pkg (CN, EU, US)
- SIZE
The filesize in a readable format, e.g. '1.1 GB'
- TITLE_XX
The title name in a specific language XX. If not available, the default
language is used.
Available language codes:
JA, EN, FR, ES, DE, IT, NL, PT, RU, KO, CH, ZH, FI, SV, DA,
NO, PL, BR, GB, TR, LA, AR, CA, CS, HU, EL, RO, TH, VI, IN
The readable name format (-n) uses the following format:
'%TITLE% (%TITLE_ID%) [v%VER%]'
Expand Down
24 changes: 24 additions & 0 deletions lib/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from argparse import RawDescriptionHelpFormatter

## argparse Help formatter to change order usage
class Formatter(RawDescriptionHelpFormatter):
# use defined argument order to display usage
def _format_usage(self, usage, actions, groups, prefix):
if prefix is None:
prefix = 'usage: '

# if usage is specified, use that
if usage is not None:
usage = usage % dict(prog=self._prog)

# if no optionals or positionals are available, usage is just prog
elif usage is None and not actions:
usage = '%(prog)s' % dict(prog=self._prog)
elif usage is None:
prog = '%(prog)s' % dict(prog=self._prog)
# build full usage string
action_usage = self._format_actions_usage(actions, groups) # NEW
usage = ' '.join([s for s in [prog, action_usage] if s])
# omit the long line wrapping code
# prefix with 'usage:'
return '%s%s\n\n' % (prefix, usage)
34 changes: 31 additions & 3 deletions lib/pkg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@

import sys, os, struct, traceback

# text of available values for help texts
AVAILABLE_VALUES = (
' Raw values from param.sfo like\n'
' - TITLE, TITLE_ID, CONTENT_ID, VERSION, APP_VER, PARENTAL_LEVEL, \n'
' SYSTEM_VER, ...\n'
' Formatted values, especially for version information:\n'
' - LANGUAGES\n'
' The list of title name languages, e.g. \'EN,FR,RU\'\n'
' This does not always reflect supported languages.'
' - VER\n'
' Equals VERSION for a game / an application and APP_VER(U) for an update\n'
' - SYS_VER\n'
' The required system version number in a readable format, e.g. \'2.70\'\n'
' - SDK_VER\n'
' The used sdk version number in a readable format - if available - e.g. \'2.70\'\n'
' - REGION\n'
' The region of the pkg (CN, EU, US)\n'
' - SIZE\n'
' The filesize in a readable format, e.g. \'1.1 GB\'\n'
' - TITLE_XX\n'
' The title name in a specific language XX. If not available, the default\n'
' language is used.\n'
'\n'
' Available language codes:\n'
' JA, EN, FR, ES, DE, IT, NL, PT, RU, KO, CH, ZH, FI, SV, DA,\n'
' NO, PL, BR, GB, TR, LA, AR, CA, CS, HU, EL, RO, TH, VI, IN'
)

## utility functions
def convert_bytes(num):
"this function will convert bytes to MB.... GB... etc"
Expand Down Expand Up @@ -230,9 +258,9 @@ def getPkgInfo(pkg_file_path):
return pkg_info

except IOError:
print 'ERROR: i/o error during processing ({})'.format(pkg_file_path)
print u'ERROR: i/o error during processing ({})'.format(pkg_file_path)
except MyError as e:
print 'ERROR: {} ({})'.format(e.message, pkg_file_path)
print u'ERROR: {} ({})'.format(e.message, pkg_file_path)
except:
print 'ERROR: unexpected error: {} ({})'.format(sys.exc_info()[0], pkg_file_path)
print u'ERROR: unexpected error: {} ({})'.format(sys.exc_info()[0], pkg_file_path)
traceback.print_exc(file=sys.stdout)
32 changes: 5 additions & 27 deletions pkg_list.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,20 @@
## pkg_list by n1ghty
REL_VERSION = 'v1.1.1'
REL_VERSION = 'v1.1.2'

import sys, os, struct, traceback, xlsxwriter, argparse
from lib import pkg_parser, xlsxlist
from lib import pkg_parser, xlsxlist, common

print 'pkg_tools / pkg_list ' + REL_VERSION + ' by n1ghty'

## parse arguments
from argparse import RawTextHelpFormatter
parser = argparse.ArgumentParser(
description = 'This tool parses all pkg files in the specified directory/directories recursively\n'
'and generates an excel sheet from the parsed infos.\n'
'\n'
'Available values for the columns:\n'
' - Values from param.sfo like\n'
' TITLE, TITLE_ID, CONTENT_ID, VERSION, APP_VER, PARENTAL_LEVEL, \n'
' SYSTEM_VER, ...\n'
' - LANGUAGES\n'
' The list of title name languages, e.g. \'EN,FR,RU\'\n'
' This does not always reflect supported languages.'
' - VER\n'
' Equals VERSION for a game / an application and APP_VER(U) for an update\n'
' - REGION\n'
' The region of the pkg (CN, EU, US)\n'
' - SIZE\n'
' The filesize in a readable format, e.g. \'1.1 GB\'\n'
' - SYS_VER\n'
' The required system version number in a readable format, e.g. \'2.70\'\n'
' - SDK_VER\n'
' The used sdk version number in a readable format, e.g. \'2.70\'\n'
' - TITLE_XX\n'
' The title name in a specific language XX. If not available, the default\n'
' language is used.\n'
'\n'
' Available language codes:\n'
' JA, EN, FR, ES, DE, IT, NL, PT, RU, KO, CH, ZH, FI, SV, DA,\n'
' NO, PL, BR, GB, TR, LA, AR, CA, CS, HU, EL, RO, TH, VI, IN',
formatter_class=argparse.RawDescriptionHelpFormatter)
+ pkg_parser.AVAILABLE_VALUES,
formatter_class=common.Formatter
)
parser.add_argument('pkg_path', type=unicode, nargs='+', help='the path(s) to scan for pkg files')
parser.add_argument('-r', dest='recursive', action='store_true', help='include subdirectories')
parser.add_argument('-c', dest='column', nargs='+', help='specify the columns')
Expand Down
54 changes: 20 additions & 34 deletions pkg_rename.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
## pkg_rename by n1ghty
REL_VERSION = 'v1.1.1'
REL_VERSION = 'v1.1.2'

import sys, os, struct, traceback, re, codecs, argparse
from lib import pkg_parser
from lib import pkg_parser, common

sys.stdout = codecs.getwriter('utf8')(sys.stdout)
sys.stderr = codecs.getwriter('utf8')(sys.stderr)
Expand All @@ -11,7 +11,6 @@
NAME_FORMAT = u'%TITLE% (%TITLE_ID%) [v%VER%]'

## parse arguments
from argparse import RawTextHelpFormatter
parser = argparse.ArgumentParser(
description = 'This tool renames PS4 pkg files to the sony format (default), a readable\n'
'name format or a custom specified format.\n'
Expand All @@ -21,33 +20,12 @@
'E.g. \'%TITLE% (%TITLE_ID%)\' will result in \'Game name (CUSA01234)\'\n'
'\n'
'Available values for formatting:\n'
' - Values from param.sfo like\n'
' TITLE, TITLE_ID, CONTENT_ID, VERSION, APP_VER, PARENTAL_LEVEL, \n'
' SYSTEM_VER, ...\n'
' - LANGUAGES\n'
' The list of title name languages, e.g. \'EN,FR,RU\'\n'
' This does not always reflect supported languages.'
' - VER\n'
' Equals VERSION for a game / an application and APP_VER(U) for an update\n'
' - REGION\n'
' The region of the pkg (CN, EU, US)\n'
' - SIZE\n'
' The filesize in a readable format, e.g. \'1.1 GB\'\n'
' - SYS_VER\n'
' The required system version number in a readable format, e.g. \'2.70\'\n'
' - SDK_VER\n'
' The used sdk version number in a readable format, e.g. \'2.70\'\n'
' - TITLE_XX\n'
' The title name in a specific language XX. If not available, the default\n'
' language is used.\n'
'\n'
' Available language codes:\n'
' JA, EN, FR, ES, DE, IT, NL, PT, RU, KO, CH, ZH, FI, SV, DA,\n'
' NO, PL, BR, GB, TR, LA, AR, CA, CS, HU, EL, RO, TH, VI, IN \n'
+ pkg_parser.AVAILABLE_VALUES +
'\n'
'The readable name format (-n) uses the following format:\n'
'\'' + NAME_FORMAT + '\'',
formatter_class=argparse.RawDescriptionHelpFormatter)
formatter_class=common.Formatter
)
parser.add_argument('pkg_path', type=unicode, help='the pkg file which shall be renamed (or directory when used with -d)')
parser.add_argument('-t', dest='testrun', action='store_true', help='only test the formatting without renaming')
parser.add_argument('-c', dest='custom_format', type=unicode, help='custom file name format')
Expand Down Expand Up @@ -85,8 +63,13 @@ def doDictFormat(s, dict):
format_val_arr = []
format_arr = re.findall('\%(.*?)\%', s)
for val in format_arr:
if (val.upper() == 'TITLE'):
title = getReadableString(dict['TITLE']).replace(': ', ' - ').replace(':','-').replace('/','_').replace('\\','_')
if (val.upper().startswith('TITLE') and val.upper() != 'TITLE_ID'):
title = getReadableString(dict[val.upper()])
# replace invalid characters for filenames
title = title.replace(': ', ' - ').replace(':','-').replace('|','l').replace('?','')
title = title.replace('/','_').replace('\\','_').replace('*','_')
title = title.replace('<','(').replace('>',')')
# replacing of reserved names like 'nul', 'com1' etc is probably not needed
s_f = s_f.replace('%' + val + '%', '{}')
format_val_arr.append(title)
elif val.upper() in dict:
Expand Down Expand Up @@ -117,16 +100,19 @@ def renamePkg(pkg_file_path):

print 'Renaming \'' + os.path.split(pkg_file_path)[1] + '\' to \'' + format_out + '\''
if (args.testrun == False):
pkg_new_file_path = os.path.dirname(os.path.abspath(pkg_file_path)) + '\\' + format_out
if os.path.exists(pkg_new_file_path):
raise pkg_parser.MyError('file \''+pkg_new_file_path+'\' already exists!')
if (os.path.split(pkg_file_path)[1] == format_out):
print ' Skipped, same filename already set.'
else:
os.rename(pkg_file_path, pkg_new_file_path)
pkg_new_file_path = os.path.dirname(os.path.abspath(pkg_file_path)) + '\\' + format_out

This comment has been minimized.

Copy link
@kkaazzee

kkaazzee Mar 21, 2018

Hi @n1ghty. I replace the \\ in a local build with os.sep. The \\ caused issues saving files on my macOS system. os.path.join might be a better alternative to concatenation.

if os.path.exists(pkg_new_file_path):
raise pkg_parser.MyError('file \''+pkg_new_file_path+'\' already exists!')

This comment has been minimized.

Copy link
@kkaazzee

kkaazzee Mar 21, 2018

Hi again @n1ghty, will you please consider silently ignoring files when the new file name matches the existing file name? For example, if pkg_rename is run against a directory of already renamed files it would no longer raise errors. Instead, errors would only be raised if the existing file and new file have different basenames.

Thanks!

else:
os.rename(pkg_file_path, pkg_new_file_path)

except pkg_parser.MyError as e:
print 'ERROR:', e.message
except:
print 'ERROR: unexpected error: {} ({})'.format(sys.exc_info()[0], pkg_file_path)
print u'ERROR: unexpected error: {} ({})'.format(sys.exc_info()[0], pkg_file_path)
traceback.print_exc(file=sys.stdout)

if (args.dir):
Expand Down

0 comments on commit 7bf1024

Please sign in to comment.