Initial commit
This commit is contained in:
commit
058e3fe762
|
@ -0,0 +1,2 @@
|
|||
/dist/
|
||||
/build/
|
|
@ -0,0 +1,16 @@
|
|||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
parse = "*"
|
||||
eyed3 = "*"
|
||||
ffmpeg-python = "*"
|
||||
pyinstaller = "*"
|
||||
mutagen = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
|
@ -0,0 +1,193 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "298dd79c62dc5270388c13321b35a57cc741bb43662b04fce47c0641ffac0f32"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.9"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"altgraph": {
|
||||
"hashes": [
|
||||
"sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa",
|
||||
"sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe"
|
||||
],
|
||||
"version": "==0.17"
|
||||
},
|
||||
"coverage": {
|
||||
"extras": [
|
||||
"toml"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
|
||||
"sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
|
||||
"sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
|
||||
"sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
|
||||
"sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
|
||||
"sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
|
||||
"sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
|
||||
"sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
|
||||
"sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
|
||||
"sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
|
||||
"sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
|
||||
"sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
|
||||
"sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
|
||||
"sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
|
||||
"sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
|
||||
"sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
|
||||
"sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
|
||||
"sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
|
||||
"sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
|
||||
"sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
|
||||
"sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
|
||||
"sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
|
||||
"sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
|
||||
"sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
|
||||
"sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
|
||||
"sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
|
||||
"sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
|
||||
"sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
|
||||
"sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
|
||||
"sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
|
||||
"sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
|
||||
"sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
|
||||
"sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
|
||||
"sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
|
||||
"sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
|
||||
"sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
|
||||
"sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
|
||||
"sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
|
||||
"sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
|
||||
"sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
|
||||
"sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
|
||||
"sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
|
||||
"sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
|
||||
"sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
|
||||
"sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
|
||||
"sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
|
||||
"sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
|
||||
"sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
|
||||
"sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
|
||||
"sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
|
||||
"sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
|
||||
"sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'",
|
||||
"version": "==5.5"
|
||||
},
|
||||
"deprecation": {
|
||||
"hashes": [
|
||||
"sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff",
|
||||
"sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"
|
||||
],
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"eyed3": {
|
||||
"hashes": [
|
||||
"sha256:4b5064ec0fb3999294cca0020d4a27ffe4f29149e8292fdf7b2de9b9cabb7518",
|
||||
"sha256:97bd529384df1c3dbdd143d86bf1705729d97d862969a214696f9e32c32b5767"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.9.6"
|
||||
},
|
||||
"ffmpeg-python": {
|
||||
"hashes": [
|
||||
"sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127",
|
||||
"sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"filetype": {
|
||||
"hashes": [
|
||||
"sha256:353369948bb1c09b8b3ea3d78390b5586e9399bff9aab894a1dff954e31a66f6",
|
||||
"sha256:da393ece8d98b47edf2dd5a85a2c8733e44b769e32c71af4cd96ed8d38d96aa7"
|
||||
],
|
||||
"version": "==1.0.7"
|
||||
},
|
||||
"future": {
|
||||
"hashes": [
|
||||
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"mutagen": {
|
||||
"hashes": [
|
||||
"sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1",
|
||||
"sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.45.1"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
|
||||
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.9"
|
||||
},
|
||||
"parse": {
|
||||
"hashes": [
|
||||
"sha256:9ff82852bcb65d139813e2a5197627a94966245c897796760a3a2a8eb66f020b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.19.0"
|
||||
},
|
||||
"pefile": {
|
||||
"hashes": [
|
||||
"sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==2019.4.18"
|
||||
},
|
||||
"pyinstaller": {
|
||||
"hashes": [
|
||||
"sha256:f5c0eeb2aa663cce9a5404292c0195011fa500a6501c873a466b2e8cad3c950c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2"
|
||||
},
|
||||
"pyinstaller-hooks-contrib": {
|
||||
"hashes": [
|
||||
"sha256:27558072021857d89524c42136feaa2ffe4f003f1bdf0278f9b24f6902c1759c",
|
||||
"sha256:892310e6363655838485ee748bf1c5e5cade7963686d9af8650ee218a3e0b031"
|
||||
],
|
||||
"version": "==2021.1"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pywin32-ctypes": {
|
||||
"hashes": [
|
||||
"sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942",
|
||||
"sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"version": "==0.10.2"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
osu!pl
|
||||
======
|
||||
Export your osu! beatmaps as Music & Video Libraries and playlists
|
||||
|
||||
Requirements
|
||||
------------
|
||||
1. Install [ffmpeg](https://www.ffmpeg.org/) to PATH
|
||||
2. For building, you gonna need pipenv: `pip install pipenv`
|
||||
|
||||
Install (Windows)
|
||||
-----------------
|
||||
Visit [releases](https://github.com/mostm/osu-pl/releases/latest), and grab latest one.
|
||||
|
||||
Build
|
||||
-----
|
||||
1. `git clone https://github.com/mostm/osu-pl.git`
|
||||
2. `pipenv sync`
|
||||
3. `pyinstaller main.spec`
|
||||
|
||||
Note: While project has been written to support multiplatform, osu!stable does not officially support multiplatform.
|
||||
|
||||
So there is no reason for me to test this under other platforms.
|
||||
Submit your pull requests, if this becomes an issue for you.
|
|
@ -0,0 +1,94 @@
|
|||
import os
|
||||
import shutil
|
||||
import configparser
|
||||
|
||||
beatmap_dir = os.path.abspath(os.environ['LOCALAPPDATA']+'\\osu!\\Songs\\')
|
||||
beatmaps = []
|
||||
bm_osu = []
|
||||
|
||||
with os.scandir(os.path.abspath(beatmap_dir)) as it:
|
||||
for entry in it:
|
||||
if entry.is_dir():
|
||||
try:
|
||||
beatmap_id = int(str(entry.name).split(' ')[0])
|
||||
except ValueError:
|
||||
# I'm not sure what to do about unranked maps right now, we will exclude them
|
||||
continue
|
||||
beatmaps.append(entry.path)
|
||||
|
||||
beatmap_type = {
|
||||
"id": 0, # You may parse for "[Metadata]\n\nBeatmapSetID:{sid}" (WARN: Earlier maps will lack this parameter (osu file format v3 < osu file format v14)) or use the one provided with path
|
||||
"name": 'Author - Title', # I should get it from osu files rather than directory, but that's how it happens
|
||||
"audio": ".\\somefile.mp3", # Parse for "[General]\n\nAudioFilename: {filename}" | DONE
|
||||
"video": ".\\something.mp4" # Parse for "[Events]\n\nVideo,{timestamp},{filename}" (found mp4,avi,mpg) | plz check, TODO
|
||||
}
|
||||
|
||||
for beatmap in beatmaps:
|
||||
with os.scandir(os.path.abspath(beatmap)) as it:
|
||||
bm = {
|
||||
'id': int(str(os.path.split(beatmap)[1]).split(' ')[0]),
|
||||
'name': str(os.path.split(beatmap)[1])[len(str(os.path.split(beatmap)[1]).split(' ')[0])+1:],
|
||||
'audio': None,
|
||||
'audio_length': None,
|
||||
'video': None
|
||||
}
|
||||
print('{} {}'.format(bm['id'], bm['name']))
|
||||
for entry in it:
|
||||
if entry.is_file():
|
||||
if entry.path.endswith('osu'):
|
||||
# ConfigParser is actually overkill solution, although I set it up to work
|
||||
# FixMe: This solution does not account for multiple (via diff) maps in one
|
||||
# Although, ranked maps should never have this.
|
||||
with open(entry.path, 'r', encoding="utf-8") as f:
|
||||
config_string = '[global]\n' + f.read()
|
||||
a = ''
|
||||
for x in config_string.split('\n')[:config_string.split('\n').index('[Events]')-1]:
|
||||
a += x+'\n'
|
||||
config = configparser.ConfigParser(allow_no_value=True)
|
||||
config.read_string(a)
|
||||
# TODO: Rewrite to simple checks and add video checking.
|
||||
bm['audio'] = os.path.abspath(os.path.dirname(entry.path)+'\\'+config.get('General', 'AudioFilename'))
|
||||
elif entry.path.endswith('mp4') or entry.path.endswith('avi') or entry.path.endswith('mpg'):
|
||||
bm['video'] = entry.path
|
||||
bm_osu.append(bm)
|
||||
|
||||
|
||||
text_playlist = ""
|
||||
for bm in bm_osu:
|
||||
if bm['audio']:
|
||||
text_playlist += "#EXTINF:0,{0}\n{1}\n".format(bm['name'], bm['audio'])
|
||||
|
||||
text_playlist = text_playlist[:-1]
|
||||
|
||||
try:
|
||||
with open('osu.m3u', 'w', encoding='utf-8') as file:
|
||||
file.write(text_playlist)
|
||||
except:
|
||||
open('osu.m3u', 'x')
|
||||
with open('osu.m3u', 'w', encoding='utf-8') as file:
|
||||
file.write(text_playlist)
|
||||
|
||||
text_type = ""
|
||||
for bm in bm_osu:
|
||||
if bm['name']:
|
||||
text_type += "{0}\n".format(bm['name'])
|
||||
text_type = text_type[:-1]
|
||||
try:
|
||||
with open('osu.txt', 'w', encoding='utf-8') as file:
|
||||
file.write(text_type)
|
||||
except:
|
||||
open('osu.txt', 'x')
|
||||
with open('osu.txt', 'w', encoding='utf-8') as file:
|
||||
file.write(text_type)
|
||||
|
||||
for bm in bm_osu:
|
||||
if bm['audio']:
|
||||
print('{} {}'.format(bm['id'], bm['name']))
|
||||
if os.path.basename(bm['audio']).split('.')[-1] != '':
|
||||
shutil.copy2(bm['audio'], "{}\\osu music\\{}.{}".format(os.getcwd(), bm['name'], os.path.basename(bm['audio']).split('.')[-1]))
|
||||
if bm['video']:
|
||||
shutil.copy2(bm['video'], "{}\\osu music\\{}.{}".format(os.getcwd(), bm['name'], os.path.basename(bm['video']).split('.')[-1]))
|
||||
|
||||
|
||||
|
||||
print('done, ty for use')
|
|
@ -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!')
|
|
@ -0,0 +1,33 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['main.py'],
|
||||
pathex=['C:\\Users\\Levent\\Stuff\\Projects\\osu!pl'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='main',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True )
|
Reference in New Issue