Browse Source

Add files via upload

main 1.3.0
Unbewohnte 4 years ago committed by GitHub
parent
commit
1e976d7f7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 90
      OBM.go
  2. 5
      README.md
  3. 38
      manager/beatmap.go
  4. 45
      manager/paths.go
  5. 71
      manager/replacer.go
  6. 73
      manager/retriever.go
  7. 111
      settings/settings.go
  8. 24
      settings/structure.go
  9. 36
      util/background.go
  10. 46
      util/checks.go
  11. 30
      util/copy.go
  12. 44
      worker.go

90
OBM.go

@ -3,13 +3,13 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"sync" "sync"
"time" "time"
"github.com/Unbewohnte/OBM/logger" "github.com/Unbewohnte/OBM/logger"
"github.com/Unbewohnte/OBM/manager" "github.com/Unbewohnte/OBM/manager"
"github.com/Unbewohnte/OBM/settings" "github.com/Unbewohnte/OBM/settings"
"github.com/Unbewohnte/OBM/util"
) )
var ( var (
@ -17,8 +17,9 @@ var (
) )
type job struct { type job struct {
songPath string beatmapFolderPath string
pathToImage string replacementImagePath string
retrievementPath string
} }
type result struct { type result struct {
@ -26,55 +27,30 @@ type result struct {
failed uint64 failed uint64
} }
// a basic implementation of a concurrent worker
func worker(jobs <-chan job, results chan result, WG *sync.WaitGroup) {
defer WG.Done()
for job := range jobs {
s, f := manager.ReplaceBackgrounds(job.songPath, job.pathToImage)
results <- result{
successful: s,
failed: f,
}
}
}
func workerPool(jobs chan job, results chan result, numOfWorkers int, WG *sync.WaitGroup) {
// check if there are less jobs than workers
if numOfWorkers > len(jobs) {
numOfWorkers = len(jobs)
}
// replacing backgrounds for each beatmap concurrently
for i := 0; i < numOfWorkers; i++ {
WG.Add(1)
go worker(jobs, results, WG)
}
}
func init() { func init() {
exists, err := settings.CheckSettingsFile() exists, err := settings.DoesExist()
if err != nil { if err != nil {
logger.LogError(true, err) logger.LogError(true, err)
} }
if exists { if !exists {
logger.LogInfo("Found settings file") // settings file does not exist, so create it and exit (assuming that this is the first run)
return settings.Create()
logger.LogInfo("Successfully created new settings file")
os.Exit(0)
} }
// settings file does not exist, so create it and exit (assuming that this is the first run) logger.LogInfo("Found settings file")
settings.CreateSettingsFile() return
os.Exit(0)
} }
func main() { func main() {
startingTime := time.Now() startingTime := time.Now()
settings := settings.GetSettings() SETTINGS := settings.Get()
// processing given settings // creating black image
if settings.CreateBlackBGImage { if SETTINGS.CreateBlackBGImage {
err := manager.CreateBlackBG(1920, 1080) err := util.CreateBlackBG(1920, 1080)
if err == nil { if err == nil {
logger.LogInfo("Successfully created black background") logger.LogInfo("Successfully created black background")
} else { } else {
@ -82,37 +58,25 @@ func main() {
} }
} }
osuSongsDir, err := manager.GetSongsDir(settings.OsuDir) beatmaps, err := manager.GetBeatmapFolderPaths(SETTINGS.OsuDir)
if err != nil {
logger.LogError(true, err)
}
if settings.ReplacementImagePath == "" || settings.ReplacementImagePath == " " {
logger.LogError(true, "Image path not specified ! Specify `pathToimage` in settings file !")
}
// reading contents of `Songs` folder
osuSongsDirContents, err := os.ReadDir(osuSongsDir)
if err != nil { if err != nil {
logger.LogError(true, fmt.Sprintf("Error reading osu songs directory : %s", err.Error())) logger.LogError(true, "Error getting beatmap folders: ", err)
} }
logger.LogInfo(fmt.Sprintf("Found %d beatmap folders", len(beatmaps)))
// creating jobs for workers // creating jobs for workers
jobs := make(chan job, len(osuSongsDirContents)) jobs := make(chan job, len(beatmaps))
for _, songDir := range osuSongsDirContents { for _, beatmap := range beatmaps {
if songDir.IsDir() { jobs <- job{
jobs <- job{ beatmapFolderPath: beatmap,
songPath: filepath.Join(osuSongsDir, songDir.Name()), replacementImagePath: SETTINGS.BackgroundReplacement.ReplacementImagePath,
pathToImage: settings.ReplacementImagePath, retrievementPath: SETTINGS.BackgroundRetrievement.RetrievementPath,
}
} }
} }
close(jobs) close(jobs)
logger.LogInfo(fmt.Sprintf("Found %d song folders", len(jobs)))
results := make(chan result, len(jobs)) results := make(chan result, len(jobs))
workerPool(jobs, results, settings.Workers, &WG) workerPool(jobs, results, SETTINGS.Workers, &WG)
WG.Wait() WG.Wait()
close(results) close(results)
@ -124,7 +88,7 @@ func main() {
} }
total := successful + failed total := successful + failed
logger.LogInfo(fmt.Sprintf("DONE in %v. %d successful (%d%%/100%%); %d failed (%d%%/100%%)", logger.LogInfo(fmt.Sprintf("DONE in %v. %d operations successful (%d%%/100%%); %d failed (%d%%/100%%)",
time.Since(startingTime), successful, successful/total*100, failed, failed/total*100)) time.Since(startingTime), successful, successful/total*100, failed, failed/total*100))
} }

5
README.md

@ -1,6 +1,6 @@
# OBM (Osu!-background-manager) # OBM (Osu!-background-manager)
## This utility will help you with replacement of Osu!`s beatmap backgrounds (and more in the future) ## This utility will help you with replacement and retrieving Osu!`s beatmap backgrounds and more in the future
**Use at your own risk !** **Use at your own risk !**
There is no way to return removed original backgrounds unless you delete all beatmaps and reimport newly downloaded versions of them again. There is no way to return removed original backgrounds unless you delete all beatmaps and reimport newly downloaded versions of them again.
@ -26,7 +26,8 @@ There is no way to return removed original backgrounds unless you delete all bea
### First run ### First run
1. The program will generate a settings.json file if it is not already in the directory when you run it 1. The program will generate a settings.json file if it is not already in the directory when you run it
2. Paste your Osu! filepath in the "pathToOsu" field 2. Paste your Osu! filepath in the "pathToOsu" field (this field is required)
3. enable/disable
3. Paste the filepath to the image in the "pathToimage" field. **ALL** beatmap`s backgrounds will be replaced with this image 3. Paste the filepath to the image in the "pathToimage" field. **ALL** beatmap`s backgrounds will be replaced with this image
4. Additionally you can disable the "createBlackBackgoundImage" by replacing **true** with **false** or change the number of workers 4. Additionally you can disable the "createBlackBackgoundImage" by replacing **true** with **false** or change the number of workers
5. Run the program once again 5. Run the program once again

38
manager/beatmap.go

@ -0,0 +1,38 @@
package manager
import (
"errors"
"os"
"strings"
"github.com/Unbewohnte/OBM/util"
)
// parses given .osu file and returns the filename of its background
func GetBackgroundName(pathTobeatmap string) (string, error) {
beatmapBytes, err := os.ReadFile(pathTobeatmap)
if err != nil {
return "", err
}
beatmapContents := string(beatmapBytes)
// get index of "[Events]" (this is where BG filename is stored)
eventsIndex := strings.Index(beatmapContents, "[Events]")
if eventsIndex == -1 {
return "", errors.New("Could not retrieve index of \"[Events]\"")
}
// get index of [TimingPoints] (this tag is right after the previous "[Events]" tag,
// so we can grab the whole "[Events]" tag contents)
timingPointsIndex := strings.Index(beatmapContents, "[TimingPoints]")
if timingPointsIndex == -1 {
return "", errors.New("Could not retrieve index of \"[TimingPoints]\"")
}
contentBetween := strings.Split(beatmapContents[eventsIndex:timingPointsIndex], ",")
for _, chunk := range contentBetween {
if util.IsImage(chunk) {
return strings.Split(chunk, "\"")[1], nil
}
}
return "", nil
}

45
manager/paths.go

@ -0,0 +1,45 @@
package manager
import (
"errors"
"fmt"
"os"
"path/filepath"
)
// filepath.Joins the main osu directory with its songs folder
func getSongsDir(baseOsuDir string) (string, error) {
songsDir := filepath.Join(baseOsuDir, "Songs")
stat, err := os.Stat(songsDir)
if err != nil {
return "", errors.New(fmt.Sprintf("Could not process the given path : %s", err))
}
if !stat.IsDir() {
return "", errors.New("Given Osu! directory is not a directory !")
}
return songsDir, nil
}
// returns an array of full filepaths to each beatmap from given base Osu! directory
func GetBeatmapFolderPaths(baseOsuDir string) ([]string, error) {
songsDir, err := getSongsDir(baseOsuDir)
if err != nil {
return nil, err
}
contents, err := os.ReadDir(songsDir)
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not read a directory : %s", err))
}
var beatmapFolderPaths []string
for _, file := range contents {
if file.IsDir() {
path := filepath.Join(songsDir, file.Name())
beatmapFolderPaths = append(beatmapFolderPaths, path)
}
}
return beatmapFolderPaths, nil
}

71
manager/replacer.go

@ -0,0 +1,71 @@
package manager
import (
"fmt"
"os"
"path/filepath"
"github.com/Unbewohnte/OBM/logger"
"github.com/Unbewohnte/OBM/util"
)
// reads contents of given dir; searches for .osu files; parses them for background info;
// removes original background and replaces it with copied version of given image
func ReplaceBackgrounds(beatmapFolder, replacementPicPath string) (successful, failed uint64) {
files, err := os.ReadDir(beatmapFolder)
if err != nil {
logger.LogError(true, fmt.Sprintf("Could not read directory : %s", err))
}
for _, file := range files {
filename := file.Name()
// if not a beatmap - skip
if !util.IsBeatmap(filename) {
continue
}
beatmap := filename
// getting BG filename
beatmapBackgroundFilename, err := GetBackgroundName(filepath.Join(beatmapFolder, beatmap))
if err != nil {
logger.LogWarning(fmt.Sprintf("BEATMAP: %s: Error getting background filename: %s", beatmap, err))
failed++
continue
}
if beatmapBackgroundFilename == "" {
logger.LogWarning(fmt.Sprintf("BEATMAP: %s Could not find background filename in this beatmap file", beatmap))
failed++
continue
}
pathToBackground := filepath.Join(beatmapFolder, beatmapBackgroundFilename)
// remove old background
err = os.Remove(pathToBackground)
if err != nil {
failed++
logger.LogWarning(fmt.Sprintf("BEATMAP: %s: Could not remove old background : %s", beatmap, err))
}
// create new background file
bgFile, err := os.Create(pathToBackground)
if err != nil {
failed++
logger.LogWarning(fmt.Sprintf("BEATMAP: %s: Could not create new background file : %s", beatmap, err))
continue
}
bgFile.Close()
// copy the contents of a given image to the newly created bg file
err = util.CopyFile(replacementPicPath, pathToBackground)
if err != nil {
logger.LogWarning(fmt.Sprintf("BEATMAP: %s: Could not copy file: %s", beatmap, err))
failed++
continue
}
successful++
}
return successful, failed
}

73
manager/retriever.go

@ -0,0 +1,73 @@
package manager
import (
"fmt"
"os"
"path/filepath"
"github.com/Unbewohnte/OBM/logger"
"github.com/Unbewohnte/OBM/util"
)
// retrieves backgrounds from given beatmap folder (same as in `ReplaceBackgrounds`) and copies them to the retrievement path
func RetrieveBackgrounds(beatmapFolder, retrievementPath string) (successful, failed uint64) {
files, err := os.ReadDir(beatmapFolder)
if err != nil {
logger.LogError(true, fmt.Sprintf("Could not read directory : %s", err))
}
for _, file := range files {
filename := file.Name()
// if not a beatmap - skip
if !util.IsBeatmap(filename) {
continue
}
beatmap := filename
// getting BG filename
beatmapBackgroundFilename, err := GetBackgroundName(filepath.Join(beatmapFolder, beatmap))
if err != nil {
logger.LogWarning(fmt.Sprintf("BEATMAP: %s: Error getting background filename: %s", beatmap, err))
failed++
continue
}
if beatmapBackgroundFilename == "" {
logger.LogWarning(fmt.Sprintf("BEATMAP: %s Could not find background filename in this beatmap file", beatmap))
failed++
continue
}
pathToBackground := filepath.Join(beatmapFolder, beatmapBackgroundFilename)
// creating a directory with the name of current beatmap folder in the retrievement path
dstPath := filepath.Join(retrievementPath, filepath.Base(beatmapFolder))
err = os.MkdirAll(dstPath, os.ModePerm)
if err != nil {
logger.LogWarning(fmt.Sprintf("BEATMAP: %s: Could not create a directory (%s) for copying", beatmap, dstPath))
continue
}
// creating a copy file
fullPathToCopy := filepath.Join(dstPath, beatmapBackgroundFilename)
dstFile, err := os.Create(fullPathToCopy)
if err != nil {
logger.LogWarning(fmt.Sprintf("BEATMAP: %s: Could not create a copy file", beatmap))
failed++
continue
}
dstFile.Close()
// copy the background file to the retrievement path
err = util.CopyFile(pathToBackground, fullPathToCopy)
if err != nil {
logger.LogWarning(fmt.Sprintf("BEATMAP: %s: Could not copy file: %s", beatmap, err))
failed++
continue
}
successful++
}
return successful, failed
}

111
settings/settings.go

@ -0,0 +1,111 @@
package settings
import (
"encoding/json"
"errors"
"fmt"
"os"
"github.com/Unbewohnte/OBM/logger"
"github.com/Unbewohnte/OBM/util"
)
const (
settingsFilename string = "settings.json"
)
// checks if the settings.json exists in current directory
func DoesExist() (bool, error) {
files, err := os.ReadDir(".")
if err != nil {
return false, errors.New(fmt.Sprintf("Unable to read current directory %s", err))
}
for _, file := range files {
if !file.IsDir() && file.Name() == settingsFilename {
return true, nil
}
}
return false, nil
}
// creates "settings.json" and sets the flag
func Create() error {
exists, err := DoesExist()
if err != nil {
return err
}
if exists {
return nil
}
file, err := os.Create(settingsFilename)
if err != nil {
return errors.New(fmt.Sprintf("Unable to create settings file : %s", err))
}
// marshaling default settings
settingsJson, err := json.MarshalIndent(Settings{
OsuDir: "",
BackgroundReplacement: backgroundReplacement{
Enabled: true,
ReplacementImagePath: "",
},
BackgroundRetrievement: backgroundRetrievement{
Enabled: false,
RetrievementPath: "",
},
CreateBlackBGImage: true,
Workers: 100,
}, "", " ")
if err != nil {
return errors.New(fmt.Sprintf("Could not marshal settings into file : %s", err))
}
file.Write(settingsJson)
file.Close()
return nil
}
// unmarshalls settings.json into struct
func Get() Settings {
settingsFileContents, err := os.ReadFile(settingsFilename)
if err != nil {
logger.LogError(true, fmt.Sprintf("Could not read settings file : %s", err))
}
var settings Settings
err = json.Unmarshal(settingsFileContents, &settings)
if err != nil {
logger.LogError(true, fmt.Sprintf("Could not unmarshal json file : %s", err))
}
// checking for edge cases or mistakes made in the settings file,
// enabled and disabled fields
if settings.BackgroundReplacement.Enabled {
if settings.BackgroundReplacement.ReplacementImagePath == "" || settings.BackgroundReplacement.ReplacementImagePath == " " {
logger.LogError(true, "`replacementImagePath` is not specified !")
} else if !util.IsImage(settings.BackgroundReplacement.ReplacementImagePath) {
logger.LogError(true, "`replacementImagePath` is pointing to a non-image file !`")
}
} else {
settings.BackgroundReplacement.ReplacementImagePath = ""
}
if settings.BackgroundRetrievement.Enabled {
if settings.BackgroundRetrievement.RetrievementPath == "" || settings.BackgroundRetrievement.RetrievementPath == " " {
logger.LogError(true, "`retrievementPath` is not specified !")
}
} else {
settings.BackgroundReplacement.ReplacementImagePath = ""
}
if settings.Workers <= 0 {
settings.Workers = 1
logger.LogWarning("`workers` is set to 0 or less. Replaced with 1")
}
return settings
}

24
settings/structure.go

@ -0,0 +1,24 @@
package settings
// the idea behind `Enabled` field is that if it`s not true - then
// we treat the path below as "" (blank string) , which workers will just ignore
// (therefore will not perform the replacement or retrievement)
type backgroundReplacement struct {
Enabled bool `json:"enabled"`
ReplacementImagePath string `json:"pathToimage"`
}
type backgroundRetrievement struct {
Enabled bool `json:"enabled"`
RetrievementPath string `json:"retrievementPath"`
}
// struct for json settings` file contents
type Settings struct {
OsuDir string `json:"pathToOsu"`
BackgroundReplacement backgroundReplacement `json:"backgroundReplacement"`
BackgroundRetrievement backgroundRetrievement `json:"backgroundRetrievement"`
CreateBlackBGImage bool `json:"createBlackBackgoundImage"`
Workers int `json:"workers"`
}

36
util/background.go

@ -0,0 +1,36 @@
package util
import (
"errors"
"fmt"
"image"
"image/color"
"image/png"
"os"
)
// creates a complete black image file
func CreateBlackBG(width, height int) error {
bgfile, err := os.Create("blackBG.png")
if err != nil {
return errors.New(fmt.Sprintf("Could not create black background file : %s", err))
}
image := image.NewRGBA(image.Rect(0, 0, width, height))
bounds := image.Bounds()
for y := 0; y < bounds.Max.Y; y++ {
for x := 0; x < bounds.Max.X; x++ {
image.Set(x, y, color.Black)
}
}
err = png.Encode(bgfile, image)
if err != nil {
return errors.New(fmt.Sprintf("Could not encode an image : %s", err))
}
err = bgfile.Close()
if err != nil {
return errors.New(fmt.Sprintf("Could not close the background file : %s", err))
}
return nil
}

46
util/checks.go

@ -0,0 +1,46 @@
package util
import (
"os"
"strings"
)
func IsDir(path string) bool {
info, err := os.Stat(path)
if err != nil {
// path error
return false
}
if !info.IsDir() {
return false
}
return true
}
// checks if given string contains ".osu" file extention
func IsBeatmap(filename string) bool {
if len(filename) < 5 {
// too short filename to be a beatmap file
return false
}
if filename[len(filename)-4:] == ".osu" {
return true
}
return false
}
// checks if given string contains the image file extention
func IsImage(filename string) bool {
if IsDir(filename) {
// given filename is actually a directory
return false
}
var imageExtentions []string = []string{"jpeg", "jpg", "png", "JPEG", "JPG", "PNG"}
for _, extention := range imageExtentions {
if strings.Contains(filename, extention) {
return true
}
}
return false
}

30
util/copy.go

@ -0,0 +1,30 @@
package util
import (
"errors"
"fmt"
"io"
"os"
)
// opens given files, copies one into another
func CopyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return errors.New(fmt.Sprintf("Could not open src file : %s", err))
}
defer srcFile.Close()
dstFile, err := os.OpenFile(dst, os.O_WRONLY, os.ModePerm)
if err != nil {
return errors.New(fmt.Sprintf("Could not open dst file : %s", err))
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return errors.New(fmt.Sprintf("Could not copy file : %s", err))
}
return nil
}

44
worker.go

@ -0,0 +1,44 @@
package main
import (
"sync"
"github.com/Unbewohnte/OBM/manager"
)
// a basic implementation of a concurrent worker
func worker(jobs <-chan job, results chan result, WG *sync.WaitGroup) {
defer WG.Done()
for job := range jobs {
var successful, failed uint64 = 0, 0
if job.retrievementPath != "" {
s, f := manager.RetrieveBackgrounds(job.beatmapFolderPath, job.retrievementPath)
successful += s
failed += f
}
if job.replacementImagePath != "" {
s, f := manager.ReplaceBackgrounds(job.beatmapFolderPath, job.replacementImagePath)
successful += s
failed += f
}
results <- result{
successful: successful,
failed: failed,
}
}
}
func workerPool(jobs chan job, results chan result, numOfWorkers int, WG *sync.WaitGroup) {
// check if there are less jobs than workers
if numOfWorkers > len(jobs) {
numOfWorkers = len(jobs)
}
// replacing backgrounds for each beatmap concurrently
for i := 0; i < numOfWorkers; i++ {
WG.Add(1)
go worker(jobs, results, WG)
}
}
Loading…
Cancel
Save