Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
GenericMadScientist committed Oct 29, 2021
0 parents commit b126f94
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Raymond Wright

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# CH FFmpeg Wrapper

A script for reducing the file size of your Clone Hero songs directory.

## Install

You need Python installed. I've developed with 3.10, recent versions should
work.

Then download or install FFmpeg. If you're on Windows, you can just use the
executable bundled with the script in Releases. If you're on Mac or Linux,
install it through your package manager so it's accessible on the command line.

## Usage

On the command line just do

```
> python compress_songs.py "<Your CH songs directory>"
```

It will convert any mp3, ogg, and wav files present to opus. Additionally, album
pngs will be converted to jpg and album jpgs and pngs will be resized so the
maximum dimension is 500, if the maximum dimension exceeds 500. For an even
smaller gain, files with the names ch.dat, notes.eof, and ps.dat will be
deleted.

This job will take a while: on my machine it takes around 7-8 hours to do my 12k
songs. And this is with my CPU usage being very high due to doing this. If you
have a better machine you may have more luck, if you have more songs it'll take
longer. I strongly suggest doing this overnight.

Finally this will make your songs incompatible with v23, due to it not
supporting the opus file format. Be aware of this before proceeding.

## Contact

Any bug reports can be filed on the GitHub page or sent to me on Discord. My
account is GMS#5303.
149 changes: 149 additions & 0 deletions compress_songs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import concurrent.futures
import os
import re
import subprocess
import sys


def get_jobs(directory):
jobs = {'album_jpgs': [], 'album_pngs': [],
'audio_files': [], 'delete': []}

for root, _, files in os.walk(directory):
for f in files:
path = os.path.join(root, f)
if f == 'album.jpg':
jobs['album_jpgs'].append(path)
continue
elif f == 'album.png':
jobs['album_pngs'].append(path)
continue
elif f in ['ch.dat', 'notes.eof', 'ps.dat']:
jobs['delete'].append(path)
continue
_, ext = os.path.splitext(path)
if ext in ['.mp3', '.ogg', '.wav']:
jobs['audio_files'].append(path)
elif ext in ['.sfk', '.sfl']:
jobs['delete'].append(path)

return jobs


def audio_to_opus(ffmpeg, file):
root, _ = os.path.splitext(file)
output = root + '.opus'
result = subprocess.run(
[ffmpeg, '-y', '-i', file, '-b:a', '80k', output], capture_output=True)
if result.returncode == 0:
os.remove(file)


def jpg_size(ffmpeg, file):
result = subprocess.run(
[ffmpeg, '-hide_banner', '-i', file], capture_output=True)
for line in result.stderr.split(b'\n'):
if line.startswith(b' Stream #0:0: Video: mjpeg ('):
sub_parts = line.split(b'), ')
if len(sub_parts) < 3:
return None
res_str = jpg_size.regex.match(sub_parts[2])
if res_str is None or res_str.start() != 0:
return None
x, _, y = res_str.group().partition(b'x')
return (int(x), int(y))
return None


jpg_size.regex = re.compile(rb'\d+x\d+')


def png_size(ffmpeg, file):
result = subprocess.run(
[ffmpeg, '-hide_banner', '-i', file], capture_output=True)
for line in result.stderr.split(b'\n'):
if line.startswith(b' Stream #0:0: Video: png'):
sub_parts = line.split(b'), ')
if len(sub_parts) < 2:
return None
res_str = png_size.regex.match(sub_parts[1])
if res_str is None or res_str.start() != 0:
return None
x, _, y = res_str.group().partition(b'x')
return (int(x), int(y))
return None


png_size.regex = re.compile(rb'\d+x\d+')


def resize_jpg(ffmpeg, file):
resolution = jpg_size(ffmpeg, file)
if resolution is None:
return
x, y = resolution
if max(x, y) <= 500:
return
max_size = max(x, y)
x = 500 * x // max_size
y = 500 * y // max_size
root, _ = os.path.splitext(file)
output = root + '-tmp-copy.jpg'
result = subprocess.run([ffmpeg,
'-y',
'-i',
file,
'-vf',
f'scale={x}:{y}',
output],
capture_output=True)
if result.returncode == 0:
os.replace(output, file)


def png_to_jpg(ffmpeg, file):
resolution = png_size(ffmpeg, file)
if resolution is None:
return
x, y = resolution
if max(x, y) > 500:
max_size = max(x, y)
x = 500 * x // max_size
y = 500 * y // max_size
root, _ = os.path.splitext(file)
output = root + '.jpg'
result = subprocess.run([ffmpeg,
'-y',
'-i',
file,
'-vf',
f'scale={x}:{y}',
output],
capture_output=True)
if result.returncode == 0:
os.remove(file)


if __name__ == '__main__':
jobs = get_jobs(sys.argv[1])
ffmpeg = 'ffmpeg.exe'
if os.name != 'nt':
ffmpeg = 'ffmpeg'

with concurrent.futures.ProcessPoolExecutor() as executor:
futures = []

for f in jobs['audio_files']:
future = executor.submit(audio_to_opus, ffmpeg, f)
futures.append(future)

for f in jobs['album_jpgs']:
future = executor.submit(resize_jpg, ffmpeg, f)
futures.append(future)

for f in jobs['album_pngs']:
future = executor.submit(png_to_jpg, ffmpeg, f)
futures.append(future)

for f in jobs['delete']:
os.remove(f)

0 comments on commit b126f94

Please sign in to comment.