3333# nuitka-project: --windows-file-description=NBSTool
3434
3535
36+ from __future__ import annotations
37+
3638import asyncio
3739import inspect
3840import json
6163from tkinter .filedialog import (askdirectory , askopenfilename ,
6264 askopenfilenames , asksaveasfilename )
6365from tkinter .messagebox import showerror , showinfo , showwarning
64- from typing import (Any , Callable , Coroutine , Deque , Iterable , List , Literal ,
65- Optional , Union , Tuple )
66+ from typing import (Any , Callable , Coroutine , Deque , Iterable , Literal ,
67+ Optional , Union )
6668
6769import pygubu
6870import pygubu .widgets .combobox
8991 # Ensure pydub.utils.which can splits the added ffmpeg path properly
9092 # Fix for the issue #10
9193 if not os .environ ["PATH" ].endswith (os .pathsep ):
92- os .environ ["PATH" ] += os .pathsep
94+ os .environ ["PATH" ] += os .pathsep
9395 # Add the path of the ffmpeg before the first pydub import statement
9496 os .environ ["PATH" ] += resource_path ('ffmpeg' , 'bin' )
9597
355357# Taken from Note Block World source code. This shouldn't violate AGPL, though.
356358# https://github.com/OpenNBS/NoteBlockWorld/blob/main/shared/features/thumbnail/index.ts#L27
357359NBS_INST_NOTE_COLORS_HEX = (
358- '#1964ac' ,
359- '#3c8e48' ,
360- '#be6b6b' ,
361- '#bebe19' ,
362- '#9d5a98' ,
363- '#572b21' ,
364- '#bec65c' ,
365- '#be19be' ,
366- '#52908d' ,
367- '#bebebe' ,
368- '#1991be' ,
369- '#be2328' ,
370- '#be5728' ,
371- '#19be19' ,
372- '#be1957' ,
373- '#575757' ,
360+ '#1964ac' ,
361+ '#3c8e48' ,
362+ '#be6b6b' ,
363+ '#bebe19' ,
364+ '#9d5a98' ,
365+ '#572b21' ,
366+ '#bec65c' ,
367+ '#be19be' ,
368+ '#52908d' ,
369+ '#bebebe' ,
370+ '#1991be' ,
371+ '#be2328' ,
372+ '#be5728' ,
373+ '#19be19' ,
374+ '#be1957' ,
375+ '#575757' ,
374376)
375- NBS_INST_NOTE_COLORS = tuple (Color (color ) for color in NBS_INST_NOTE_COLORS_HEX )
376- NBS_NOTE_COLORS_TO_INST = {color .to_string (hex = True ): index for index , color in enumerate (NBS_INST_NOTE_COLORS )}
377+ NBS_INST_NOTE_COLORS = tuple (Color (color )
378+ for color in NBS_INST_NOTE_COLORS_HEX )
379+ NBS_NOTE_COLORS_TO_INST = {color .to_string (
380+ hex = True ): index for index , color in enumerate (NBS_INST_NOTE_COLORS )}
377381
378382NBSTOOL_FIRST_COMMIT_TIMESTAMP = 1565100420
379383CONSONANTS = "bcdfghjklmnpqrstvwxyzBCDFGHJLKMNPQRSTVWXYZ"
@@ -496,7 +500,7 @@ def initVarsAndCallbacksFrom(self, builder: Builder):
496500
497501 def isInteger (self , value : str ) -> bool :
498502 return value == '' or value .isdigit ()
499-
503+
500504 def isRequiredInteger (self , value : str ) -> bool :
501505 return value .isdigit ()
502506
@@ -760,11 +764,14 @@ def initArrangeTab(self):
760764 def intImportImageTab (self ):
761765 self .pixelArtImagePathVar .set ('' )
762766 self .imgInsertPosVar .set ('lastLayer' )
763- pathChooser : PathChooserInput = self .builder .get_object ('pixelArtImagePathChooser' )
764- filetypes = [('Image files' , '*.png *.jpg *.jpeg *.gif *.bmp *.tiff *.webp' ), ('All files' , '*' )]
767+ pathChooser : PathChooserInput = self .builder .get_object (
768+ 'pixelArtImagePathChooser' )
769+ filetypes = [
770+ ('Image files' , '*.png *.jpg *.jpeg *.gif *.bmp *.tiff *.webp' ), ('All files' , '*' )]
765771 pathChooser .config (filetypes = filetypes , width = 200 )
766- sizeCombo : PygubuCombobox = self .builder .get_object ('imgInsertSizeCombo' )
767- options : List = []
772+ sizeCombo : PygubuCombobox = self .builder .get_object (
773+ 'imgInsertSizeCombo' )
774+ options : list = []
768775 for zoom in NBW_THUMBNAIL_ZOOM_PERCENT_VALUES :
769776 # zoom = 400% => width = 10, height = 6
770777 # zoom = 200% => width = 20, height = 12
@@ -920,9 +927,10 @@ def onClose(self, event=None):
920927 def onArrangeModeChanged (self ):
921928 self .builder .get_object ('arrangeGroupPrec' )['state' ] = 'normal' if (
922929 self .arrangeMode .get () == 'instruments' ) else 'disabled'
923-
930+
924931 def onImgInsertPosChanged (self ):
925- state = 'normal' if (self .imgInsertPosVar .get () == 'custom' ) else 'disabled'
932+ state = 'normal' if (self .imgInsertPosVar .get ()
933+ == 'custom' ) else 'disabled'
926934 self .builder .get_object ('imgInsertCustomLabel1' )['state' ] = state
927935 self .builder .get_object ('imgInsertCustomLabel2' )['state' ] = state
928936 self .builder .get_object ('imgInsertPosTickSpin' )['state' ] = state
@@ -945,6 +953,12 @@ async def work(dialog: ProgressDialog):
945953 try :
946954 notebook : ttk .Notebook = get_object ('headerNotebook' )
947955 headerModifiable = notebook .index (notebook .select ()) == 1
956+ insertImageNotes : list [Note ] = []
957+ if (imgPath := self .pixelArtImagePathVar .get ()) and imgPath .strip () != '' :
958+ zoomPercent = self .imgInsertSizePercentVar .get ()
959+ insertWidth = NBW_WIDTH_DIVISOR // zoomPercent
960+ insertHeight = NBW_HEIGHT_DIVISOR // zoomPercent
961+ insertImageNotes = self .imageToNotes (imgPath , insertWidth , insertHeight )
948962 i = 0
949963 for i , index in enumerate (selectedIndexes ):
950964 dialog .totalProgress .set (i )
@@ -1006,8 +1020,8 @@ async def work(dialog: ProgressDialog):
10061020 layer .volume = 100
10071021 if applyPan :
10081022 layer .pan = 0
1009-
1010- if ( imgPath := self . pixelArtImagePathVar . get ()) and imgPath != '' :
1023+
1024+ if insertImageNotes :
10111025 insertPos = self .imgInsertPosVar .get ()
10121026 insertTick : int = - 1
10131027 insertLayer : int = - 1
@@ -1020,12 +1034,9 @@ async def work(dialog: ProgressDialog):
10201034 elif insertPos == 'custom' :
10211035 insertTick = self .imgInsertPosTickVar .get ()
10221036 insertLayer = self .imgInsertPosLayerVar .get ()
1023-
1024- zoomPercent = self .imgInsertSizePercentVar .get ()
1025- insertWidth = NBW_WIDTH_DIVISOR // zoomPercent
1026- insertHeight = NBW_HEIGHT_DIVISOR // zoomPercent
10271037
1028- self .insertImage (imgPath , songData , insertTick , insertLayer , insertWidth , insertHeight )
1038+ self .shiftNotes (insertImageNotes , insertTick , insertLayer )
1039+ songData .notes .extend (insertImageNotes )
10291040
10301041 dialog .setCurrentPercentage (randint (75 , 85 ))
10311042 await sleep (0.001 )
@@ -1037,7 +1048,8 @@ async def work(dialog: ProgressDialog):
10371048 self .songsData [k ] = v
10381049 dialog .totalProgress .set (i + 1 )
10391050
1040- showinfo ("Applying tools" , "All selected files have been processed." )
1051+ showinfo ("Applying tools" ,
1052+ "All selected files have been processed." )
10411053 except CancelledError :
10421054 raise
10431055 except Exception as e :
@@ -1065,11 +1077,12 @@ def collapseNotes(self, notes) -> None:
10651077 note .layer = layer
10661078 prevNote = note
10671079
1068- def insertImage (self , imgPath : str , song : NbsSong , x : int , y : int , width : int , height : int ) -> None :
1080+ def imageToNotes (self , imgPath : str , width : int , height : int ) -> list [ Note ] :
10691081 img = Image .open (imgPath )
10701082 img .thumbnail ((width , height ), Image .Resampling .NEAREST )
10711083 width , height = img .size
10721084 img = img .convert ('RGBA' )
1085+ notes = []
10731086
10741087 for i in range (width ):
10751088 for j in range (height ):
@@ -1078,9 +1091,18 @@ def insertImage(self, imgPath: str, song: NbsSong, x: int, y: int, width: int, h
10781091 r , g , b , a = color
10791092 if a <= 25 :
10801093 continue
1081- nearestColor = Color ('srgb' , (r / 255 , g / 255 , b / 255 )).closest (NBS_INST_NOTE_COLORS )
1082- inst = NBS_NOTE_COLORS_TO_INST [nearestColor .to_string (hex = True )]
1083- song .notes .append (Note (x + i , y + j , inst , 0 , 0 ))
1094+ nearestColor = Color (
1095+ 'srgb' , (r / 255 , g / 255 , b / 255 )).closest (NBS_INST_NOTE_COLORS )
1096+ inst = NBS_NOTE_COLORS_TO_INST [nearestColor .to_string (
1097+ hex = True )]
1098+ notes .append (Note (i , j , inst , 0 , 0 ))
1099+
1100+ return notes
1101+
1102+ def shiftNotes (self , notes : list [Note ], x : int , y : int ) -> None :
1103+ for note in notes :
1104+ note .tick += x
1105+ note .layer += y
10841106
10851107 def modifyHeaders (self , header ):
10861108 def setAttrFromStrVar (key : str , value : str ):
@@ -1463,7 +1485,7 @@ def __init__(self, master, parent):
14631485
14641486def parseFilePaths (string : str ) -> tuple :
14651487 strLen = len (string )
1466- ret : List [str ] = []
1488+ ret : list [str ] = []
14671489 filePath = ''
14681490 isQuoted = False
14691491 for i , char in enumerate (string ):
@@ -1666,19 +1688,20 @@ async def convert(self, filepath: str, dialog: ProgressDialog) -> NbsSong:
16661688 ) if not self .autoExpand .get () else 0
16671689 fadeOut = self .trailingNoteVelMode .get () == 'fadeOut'
16681690 return await midi2nbs (filepath , expandMult , self .importDuration .get (),
1669- self .durationSpacing .get (), self .trailingVelocity .get (), self .trailingVelAsPercent .get (), fadeOut , self .applyStereo .get (), self .importVelocity .get (),
1670- self .importPanning .get (), self .importPitch .get (),
1671- dialog )
1691+ self .durationSpacing .get (), self .trailingVelocity .get (), self .trailingVelAsPercent .get (
1692+ ), fadeOut , self .applyStereo .get (), self .importVelocity .get (),
1693+ self .importPanning .get (), self .importPitch .get (),
1694+ dialog )
16721695
16731696 def autoExpandChanged (self ):
16741697 self .builder .get_object ('expandScale' )[
16751698 'state' ] = 'disabled' if self .autoExpand .get () else 'normal'
1676-
1699+
16771700 def trailingNoteVelModeChanged (self ):
1678- fixedVelocity = self .trailingNoteVelMode .get () == 'fixed'
1679- self .builder .get_object ('trailingVelocitySpin' )[
1701+ fixedVelocity = self .trailingNoteVelMode .get () == 'fixed'
1702+ self .builder .get_object ('trailingVelocitySpin' )[
16801703 'state' ] = 'normal' if fixedVelocity else 'disabled'
1681- self .builder .get_object ('trailingVelPercentCheck' )[
1704+ self .builder .get_object ('trailingVelPercentCheck' )[
16821705 'state' ] = 'normal' if fixedVelocity else 'disabled'
16831706
16841707 def importDurationChanged (self ):
@@ -1697,8 +1720,10 @@ def importDurationChanged(self):
16971720 if self .importDuration .get ():
16981721 self .trailingNoteVelModeChanged ()
16991722 else :
1700- self .builder .get_object ('trailingVelocitySpin' )['state' ] = 'disabled'
1701- self .builder .get_object ('trailingVelPercentCheck' )['state' ] = 'disabled'
1723+ self .builder .get_object ('trailingVelocitySpin' )[
1724+ 'state' ] = 'disabled'
1725+ self .builder .get_object ('trailingVelPercentCheck' )[
1726+ 'state' ] = 'disabled'
17021727
17031728 def applyStereoChanged (self ):
17041729 pass
@@ -2103,7 +2128,8 @@ def main() -> None:
21032128 logger .info ("NBSTool v{}" , __version__ )
21042129 logger .info ("Platform: {}" , platform .platform ())
21052130 logger .info ("Python: {}" , sys .version )
2106- logger .info ("Architecture: {}" , "64-bit" if sys .maxsize > 2 ** 32 else "32-bit" )
2131+ logger .info ("Architecture: {}" , "64-bit" if sys .maxsize >
2132+ 2 ** 32 else "32-bit" )
21072133 if '__compiled__' in globals ():
21082134 logger .info ("Running in Nuitka-compiled mode" )
21092135 logger .info ("Nuitka version: {}" , version ('nuitka' ))
0 commit comments