Skip to content
This repository was archived by the owner on Jan 6, 2021. It is now read-only.

Commit dfb720b

Browse files
authored
Merge pull request #11 from IceDynamix/add-command-line
Add command line functionality
2 parents 001df09 + 2fbf7c4 commit dfb720b

File tree

9 files changed

+1493
-926
lines changed

9 files changed

+1493
-926
lines changed

README.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,37 @@ Download the lastest release from [here](https://github.com/IceDynamix/qua2osu/r
1212

1313
## Step by step instructions
1414

15-
* Download qua2osu, no python needed etc.
16-
* GUI (Graphical User Interface) should pop up, select a folder with your .qp files and select a folder to output your .osz files
17-
* Choose available options (currently od, hp, hs volume, hs sample set)
18-
* Click convert
19-
* Done
15+
* Download qua2osu, no installation needed etc.
16+
* You can decide to use the command-line tool or the GUI (Graphical User Interface)
17+
* Command-line:
18+
* Execute the qua2osu.exe file with the --help flag in the console by running `qua2osu.exe --help` (double-clicking doesn't work)
19+
* Set options with some flags to spice things up (Example: `qua2osu.exe -i myfolder/subfolder -od 8.5 -hp 9 -hv 0` which would set the input folder to "myfolder/subfolder", OD to 8.5, HP to 9 and hitsound volume to 0)
20+
* GUI:
21+
* Execute the qua2osu-gui.exe file, either by double-clicking or by running it in the console
22+
* GUI should pop up, select a folder with your .qp files and select a folder to output your .osz files
23+
* Set some settings and click on convert
2024

2125
## Step by step instructions to build the project yourself
2226

2327
* Install [Git](https://git-scm.com/) and [Python](https://www.python.org/) if necessary
2428
* Install [pip](https://pip.pypa.io/en/stable/installing/) if necessary (should ship with python)
2529
* Clone this repo: `git clone https://github.com/IceDynamix/qua2osu.git`
2630
* *It's best to set up a [virtual environment](https://docs.python.org/3/tutorial/venv.html) for the project, but not necessary if you don't know how to*
27-
* Activate your virtual environment by running the activate file in your virtual environment folder
31+
* *Activate your virtual environment by running the activate file in your virtual environment folder*
2832
* Run `pip install -r requirements.txt` in the directory to install all package dependencies (mainly PyYaml (.qua parser), PyQT5 (gui) and some QOL stuff)
29-
* Run `py qua2osu.py` or `python3 qua2osu.py`
30-
* Enjoy
33+
* Run `py qua2osu.py` or `py qua2osu-gui.py`
3134

3235
## Documentation
3336

3437
This project uses [pycco](https://github.com/pycco-docs/pycco) to create documentation.
3538
Regenerating the documentation after modifying or adding new files is done by `pycco yourpythonfile.py`.
3639

37-
* [Documentation of convert.py](https://icedynamix.github.io/qua2osu/convert.html)
38-
* [Documentation of qua2osu.py](https://icedynamix.github.io/qua2osu/qua2osu.html)
40+
Documentation of:
41+
42+
* [constants.py](https://icedynamix.github.io/qua2osu/constants.html)
43+
* [conversion.py](https://icedynamix.github.io/qua2osu/conversion.html)
44+
* [qua2osu.py](https://icedynamix.github.io/qua2osu/qua2osu.html)
45+
* [qua2osu-gui.py](https://icedynamix.github.io/qua2osu/qua2osu-gui.html)
3946

4047
## Contributing
4148

@@ -50,11 +57,11 @@ In case you want to contribute to this project *(which I highly doubt to be perf
5057

5158
## Notes
5259

53-
The GUI will choose `.\samples` as the default input path and `.\output` as the default output path, in case you don't select any paths.
60+
The GUI will choose `./input` as the default input path and `./output` as the default output path, in case you don't select any paths.
5461

5562
Please report issues [here on github](https://github.com/IceDynamix/qua2osu/issues).
5663

57-
## referenced games
64+
## Referenced games
5865

5966
* [**Quaver**](https://quavergame.com/)
6067
* [**osu!**](https://osu.ppy.sh/)

constants.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""General constants that are needed in most places"""
2+
3+
# Hitsounds enum
4+
HIT_SOUNDS = {
5+
"Normal": 1,
6+
"Whistle": 2,
7+
"Finish": 4,
8+
"Clap": 8
9+
}
10+
11+
# Samplesets list
12+
SAMPLESETS = [
13+
"Soft",
14+
"Normal",
15+
"Drum"
16+
]
17+
18+
19+
# Dictionary for attributes that exist in both games, but are named differently
20+
21+
# quaverAttribute -> osuAttribute
22+
23+
RENAMES = {
24+
25+
"general": {
26+
"AudioFile": "AudioFilename",
27+
"SongPreviewTime": "PreviewTime"
28+
},
29+
30+
"metadata": {
31+
"DifficultyName": "Version",
32+
"SongPreviewTime": "PreviewTime"
33+
},
34+
35+
}
36+
37+
# For attributes that should be left the way they are.
38+
39+
# attribute -> default value
40+
41+
DEFAULT_VALUES = {
42+
43+
"general": {
44+
"AudioLeadIn": 0,
45+
"Countdown": 0,
46+
"StackLeniency": 0.7,
47+
"Mode": 3, # mania
48+
"LetterboxInBreaks": 0,
49+
"SpecialStyle": 0,
50+
"WidescreenStoryboard": 0
51+
},
52+
53+
"editor": {
54+
# If bookmarks were to be set to None,
55+
# it would print out "None" when casted to a string
56+
"Bookmarks": "",
57+
"DistanceSpacing": 1.5,
58+
"BeatDivisor": 4,
59+
"GridSize": 4,
60+
"TimelineZoom": 2.5
61+
},
62+
63+
"metadata": {
64+
"BeatmapID": 0, # unsubmitted
65+
"BeatmapSetID": -1
66+
},
67+
68+
"difficulty": {
69+
"ApproachRate": 5,
70+
"SliderMultiplier": 1.4,
71+
"SliderTickRate": 1
72+
},
73+
74+
}
75+
76+
# Attributes of the command line arguments
77+
78+
# No documentation is really needed because there's already a description
79+
# for every argument
80+
81+
COMMAND_LINE_ARGS = {
82+
83+
"input": {
84+
"shortFlag": "-i",
85+
"longFlag": "--input",
86+
"required": False,
87+
"description": "Path of the input folder, defaults to ./input",
88+
"default": "input",
89+
"type": str
90+
},
91+
92+
"output": {
93+
"shortFlag": "-o",
94+
"longFlag": "--output",
95+
"required": False,
96+
"description": "Path of the output folder, defaults to ./output",
97+
"default": "output",
98+
"type": str
99+
},
100+
101+
"overall_difficulty": {
102+
"shortFlag": "-od",
103+
"longFlag": "--overall-difficulty",
104+
"required": False,
105+
"description": "Overall difficulty as an integer between 0 and 10, defaults to 8",
106+
"default": 8,
107+
"type": float,
108+
"min": 0,
109+
"max": 10
110+
},
111+
112+
"hp_drain": {
113+
"shortFlag": "-hp",
114+
"longFlag": "--hp-drain",
115+
"required": False,
116+
"description": "HP drain as an integer between 0 and 10, defaults to 8",
117+
"default": 8,
118+
"type": float,
119+
"min": 0,
120+
"max": 10
121+
},
122+
123+
"hitsound_volume": {
124+
"shortFlag": "-hv",
125+
"longFlag": "--hitsound-volume",
126+
"required": False,
127+
"description": "Hitsound volume as an integer between 0 and 100, defaults to 20",
128+
"default": 20,
129+
"type": int,
130+
"min": 0,
131+
"max": 100
132+
},
133+
134+
"sampleset": {
135+
"shortFlag": "-hs",
136+
"longFlag": "--sampleset",
137+
"required": False,
138+
"description": "Hitsound sample set as either 'Soft', 'Normal' or 'Drum', defaults to Soft",
139+
"default": "Soft",
140+
"type": str,
141+
"list": SAMPLESETS
142+
}
143+
144+
145+
}

convert.py renamed to conversion.py

Lines changed: 2 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,11 @@
11
# ## Imports
22
import math # for calculations
33
import os # for paths and directories
4-
import re # for regular expressions
5-
import time # to measure execution time
64
import zipfile # to handle .zip files (.qua and .osz)
75

86
import yaml # to parse .yaml files (.qua)
97

10-
11-
# ## Constants
12-
13-
REGEX_ILLEGAL_CHARACTERS = re.compile(r"[<>:\"/\\|\?\*]")
14-
15-
# For attributes that exist in both games but are named differently.
16-
17-
# Format: QuaverAttribute -> osuAttribute
18-
19-
RENAMES = {
20-
21-
"general": {
22-
"AudioFile": "AudioFilename",
23-
"SongPreviewTime": "PreviewTime"
24-
},
25-
26-
"metadata": {
27-
"DifficultyName": "Version",
28-
"SongPreviewTime": "PreviewTime"
29-
},
30-
31-
}
32-
33-
# For attributes that should be left the way they are.
34-
35-
# Format: QuaverAttribute -> osuAttribute
36-
37-
DEFAULT_VALUES = {
38-
39-
"general": {
40-
"AudioLeadIn": 0,
41-
"Countdown": 0,
42-
"StackLeniency": 0.7,
43-
"Mode": 3,
44-
"LetterboxInBreaks": 0,
45-
"SpecialStyle": 0,
46-
"WidescreenStoryboard": 0
47-
},
48-
49-
"editor": {
50-
# If bookmarks were to be set to None,
51-
# it would print out "None" when casted to a string
52-
"Bookmarks": "",
53-
"DistanceSpacing": 1.5,
54-
"BeatDivisor": 4,
55-
"GridSize": 4,
56-
"TimelineZoom": 2.5
57-
},
58-
59-
"metadata": {
60-
"BeatmapID": 0,
61-
"BeatmapSetID": -1
62-
},
63-
64-
"difficulty": {
65-
"ApproachRate": 5,
66-
"SliderMultiplier": 1.4,
67-
"SliderTickRate": 1
68-
}
69-
}
70-
71-
72-
HIT_SOUNDS = {
73-
"Normal": 1,
74-
"Whistle": 2,
75-
"Finish": 4,
76-
"Clap": 8
77-
}
78-
8+
from constants import *
799

8010
# ## Functions
8111

@@ -477,7 +407,7 @@ def convertQua2Osu(fileContent: str, options: object) -> str:
477407
return osu + "\n"
478408

479409

480-
def convertMapset(path: str, outputFolder: str, options=None) -> None:
410+
def convertMapset(path: str, outputFolder: str, options) -> None:
481411
"""Converts a whole .qp mapset to a .osz mapset
482412
483413
Moves all files to a new directory and converts all .qua files to .osu files
@@ -500,16 +430,6 @@ def convertMapset(path: str, outputFolder: str, options=None) -> None:
500430
folderName = "q_" + os.path.basename(path).replace(".qp", "")
501431
outputPath = os.path.join(outputFolder, folderName)
502432

503-
# Default options in case options aren't given (in this file's `main()`
504-
# for example)
505-
if options is None:
506-
options = {
507-
"od": 8,
508-
"hp": 8,
509-
"hitSoundVolume": 20,
510-
"sampleSet": "Soft"
511-
}
512-
513433
# Opens the .qp (.zip) mapset file and extracts it into a folder in the same directory
514434
with zipfile.ZipFile(path, "r") as oldDir:
515435
oldDir.extractall(outputPath)
@@ -535,49 +455,3 @@ def convertMapset(path: str, outputFolder: str, options=None) -> None:
535455
for root, dirs, files in os.walk(outputPath):
536456
for file in files:
537457
newDir.write(os.path.join(root, file), file)
538-
539-
540-
# ### Main
541-
542-
543-
def main():
544-
"""Runs a basic conversion of the samples folder for testing"""
545-
546-
inputPath = "samples"
547-
outputPath = "output"
548-
549-
qpFilesInInputDir = []
550-
551-
# Filters for all files that end with .qp and puts the
552-
# complete path of the files into an array
553-
for file in os.listdir(inputPath):
554-
path = os.path.join(inputPath, file)
555-
556-
if (file.endswith('.qp') and os.path.isfile(path)):
557-
qpFilesInInputDir.append(file)
558-
559-
numberOfQpFiles = len(qpFilesInInputDir)
560-
561-
if numberOfQpFiles == 0:
562-
print("No mapsets found in " + inputPath)
563-
564-
else:
565-
# Starts the timer for the total execution time
566-
start = time.time()
567-
568-
# Run the conversion for each .qp file
569-
for file in qpFilesInInputDir:
570-
filePath = os.path.join(inputPath, file)
571-
print(f"Converting {filePath}")
572-
convertMapset(filePath, outputPath, None)
573-
574-
# Stops the timer for the total execution time
575-
end = time.time()
576-
timeElapsed = round(end - start, 2)
577-
578-
print("Finished converting all mapsets, "
579-
f"total time elapsed: {timeElapsed} seconds")
580-
581-
582-
if __name__ == '__main__':
583-
main()

0 commit comments

Comments
 (0)