Initial commit

This commit is contained in:
2021-06-23 13:35:35 +05:00
commit 058e3fe762
7 changed files with 577 additions and 0 deletions

216
main.py Normal file
View File

@@ -0,0 +1,216 @@
import os
import shutil
from pathlib import Path
import mutagen
from mutagen.easyid3 import EasyID3
from mutagen.id3 import ID3, APIC
import ffmpeg
import mimetypes
EasyID3.RegisterTextKey('comment', 'COMM')
def parse_beatmap(content):
section = None
section_content = None
pure_sections = ['events', 'timingpoints']
beatmap = {}
for line in content.split('\n'):
if line.startswith('//'):
continue
if line.startswith('['):
if section and section_content:
beatmap[section] = section_content
section = line[1:line.index(']')].lower()
section_content = dict()
if section in pure_sections:
section_content = list()
continue
if section in pure_sections:
section_content.append(line)
elif section and ':' in line:
key = line[:line.index(':')].lower()
value = line[line.index(':') + 1:].strip()
section_content[key] = value
return beatmap
def scan_beatmaps(root):
beatmap_sets = {}
for beatmap_path in Path(root).glob('**/*.osu'):
file = open(str(beatmap_path), 'r', encoding='utf-8').read()
beatmap = parse_beatmap(file)
beatmap_set = beatmap['metadata'].get('beatmapsetid')
if not beatmap['metadata'].get('beatmapsetid'):
p = beatmap_path.parent
beatmap_set = p.name.split(' ')[0]
if not beatmap_set.isdigit():
beatmap_set = 'Unranked'
bg = None
video = None
for event in beatmap['events']:
if event.startswith('0,0,'):
bg = event.split(',')[2].strip('"').strip()
if event.startswith('Video'):
video = [x.strip('"') for x in event.split(',')[1:]]
video = {'timing': video[0], 'filename': video[1]}
beatmap['background'] = bg
beatmap['video'] = video
beatmap['path'] = str(beatmap_path)
if not beatmap_sets.get(beatmap_set):
beatmap_sets[beatmap_set] = list()
beatmap_sets[beatmap_set].append(beatmap)
return beatmap_sets
def generalize_beatmap_sets(beatmap_sets):
# Generalize to only relevant data
generalized = {}
unique_warn = []
if beatmap_sets.get('Unranked'):
beatmap_sets.pop('Unranked')
for setid in beatmap_sets.keys():
set_data = {}
for map in beatmap_sets[setid]:
data = map['metadata']
data.pop('version')
if data.get('beatmapid'):
data.pop('beatmapid')
data.pop('beatmapsetid')
beatmap_dir = Path(map['path']).parent
data['audio'] = None
if map['general']['audiofilename']:
data['audio'] = str(beatmap_dir.joinpath(map['general']['audiofilename']).absolute())
data['video'] = map['video']
if data['video']:
data['video']['filename'] = str(beatmap_dir.joinpath(data['video']['filename']).absolute())
if not os.path.exists(data['video']['filename']):
if setid not in unique_warn:
print(f'Video for {setid} is mentioned, but doesn\'t exist!')
unique_warn.append(setid)
data['video'] = None
data['thumbnail'] = None
if map['background']:
data['thumbnail'] = str(beatmap_dir.joinpath(map['background']).absolute())
for k, v in data.items():
if not v:
continue
if not set_data.get(k) or all([k == 'thumbnail', v != '', v]):
set_data[k] = v
if set_data.get(k) != v:
if k == 'tags' and len(v) > len(set_data.get(k)):
set_data[k] = v
# print(f"{map['path']}: Conflict of data with set ({k}) | {set_data.get(k)} != {v}")
generalized[setid] = set_data
return generalized
def clean_and_allow_filename(dirty_filename, invalid='<>:"/\|?*'):
fn = str(dirty_filename)
for char in invalid:
fn = fn.replace(char, '')
return fn
def generate_library(beatmap_sets, music=True, video=False, music_target=None, video_target=None):
if music:
if not music_target:
music_target = f'{os.getcwd()}{os.path.sep}osu!MusicLibrary'
try:
os.mkdir(f"{music_target}")
except:
pass
for setid, beatmap in beatmap_sets.items():
try:
map = dict(beatmap)
dir_name = f"{map['title']} by {map['artist']} ({map['creator']})"
dir_name = clean_and_allow_filename(dir_name)
try:
os.mkdir(f"{music_target}{os.path.sep}{dir_name}")
except:
pass
if not map['audio'] or map['audio'] == 'virtual':
continue
ext = os.path.splitext(map['audio'])[1]
fn = f"{map['artist']} - {map['title']}{ext}"
fn = clean_and_allow_filename(fn)
file_target = f"{music_target}{os.path.sep}{dir_name}{os.path.sep}{fn}"
shutil.copy2(map['audio'], file_target)
map['audio'] = file_target
if map.get('thumbnail') and os.path.exists(map['thumbnail']):
ext = os.path.splitext(map['thumbnail'])[1]
file_target = f"{music_target}{os.path.sep}{dir_name}{os.path.sep}cover{ext}"
shutil.copy2(map['thumbnail'], file_target)
map['thumbnail'] = file_target
audiofile = mutagen.File(map['audio'], easy=True)
if audiofile:
audiofile['artist'] = map['artist']
audiofile['album'] = map['creator']
audiofile['albumartist'] = map.get('artistunicode') if map.get('artistunicode') else map['artist']
audiofile['title'] = map['title']
if map.get('tags'):
audiofile['comment'] = map['tags']
audiofile['tracknumber'] = ['1', '1']
audiofile.save()
if map.get('thumbnail') and not map['audio'].endswith('.ogg'):
audio = mutagen.File(map['audio'], easy=False)
if 'audio/vorbis' in audio.mime:
continue
with open(map.get('thumbnail'), 'rb') as albumart:
audio['APIC'] = APIC(
encoding=3,
mime=mimetypes.guess_type(map.get('thumbnail')),
type=3, desc='osu! Beatmap Thumbnail',
data=albumart.read()
)
audio.save()
except Exception as error:
print(f'[Music] Failure while processing {setid} | {type(error)} | {str(error)}')
if video:
if not video_target:
video_target = f'{os.getcwd()}{os.path.sep}osu!VideoLibrary'
try:
os.mkdir(f"{video_target}")
except:
pass
for setid, beatmap in beatmap_sets.items():
try:
if not beatmap.get('video') or beatmap.get('audio').endswith('virtual'):
continue
map = dict(beatmap)
fn = f"{map['artist']} - {map['title']} ({map['creator']}).mp4"
output_fp = f"{video_target}{os.path.sep}{clean_and_allow_filename(fn)}"
audio_in = ffmpeg.input(map['audio'])['a']
kw = {}
if map['video']['timing'] != '0':
kw['itsoffset'] = float(map['video']['timing']) / 1000
video_in = ffmpeg.input(map['video']['filename'], **kw)
streams = ffmpeg.probe(map['video']['filename'])['streams']
ow = False
for stream in streams:
if stream.get('codec_name') in ['h264', 'avc1', 'mpeg4']:
video_in = video_in[str(stream['index'])]
ow = True
if not ow:
for stream in streams:
if stream.get('codec_name') not in ['h264', 'avc1', 'mpeg4']:
output_fp = output_fp[:-len('mp4')] + 'mkv'
out = ffmpeg.output(audio_in, video_in, output_fp, vcodec='copy', acodec='copy', fflags='+genpts')
print(' '.join(out.compile()))
out.run(overwrite_output=True)
except Exception as error:
print(f'[Video] Failure while processing {setid} | {type(error)} | {str(error)}')
return
if __name__ == '__main__':
root = os.path.abspath(os.environ['LOCALAPPDATA'] + '\\osu!\\Songs\\')
print('Scanning Beatmaps')
beatmap_sets = scan_beatmaps(root)
print('Generalizing beatmap data')
beatmaps = generalize_beatmap_sets(beatmap_sets)
print('Generating Music & Video Libraries')
generate_library(beatmaps,
music=input('Music Library? (y/n) ').lower().startswith('y'),
video=input('Video Library? (y/n) ').lower().startswith('y'))
print('Done, thanks for usage!')