1 # -*- coding: utf-8 -*-
3 Boîte de dialogue pour sélection des chansons.
10 from pygame
.locals import K_RETURN
11 from pgu
.gui
import FileDialog
12 import pgu
.gui
.basic
as basic
13 import pgu
.gui
.input as input
14 import pgu
.gui
.button
as button
15 import pgu
.gui
.pguglobals
as pguglobals
16 import pgu
.gui
.table
as table
17 import pgu
.gui
.area
as area
18 from pgu
.gui
.const
import *
19 from pgu
.gui
.dialog
import Dialog
23 from xml
.etree
import ElementTree
24 from minwii
.musicxml
import musicXml2Song
26 INDEX_TXT
= 'index.txt'
27 PICTURE_ITEM_SIZE
= 64
29 class FileOpenDialog(FileDialog
):
33 def __init__(self
, path
):
35 if not path
: self
.curdir
= os
.getcwd()
36 else: self
.curdir
= path
37 self
.dir_img
= basic
.Image(
38 pguglobals
.app
.theme
.get(cls1
+'.folder', '', 'image'))
39 self
.soundfile_img
= basic
.Image(
40 pguglobals
.app
.theme
.get(cls1
+'.soundfile', '', 'image'))
41 td_style
= {'padding_left': 4,
45 self
.title
= basic
.Label("Ouvrir un chanson", cls
="dialog.title.label")
46 self
.body
= table
.Table()
47 self
.list = area
.List(width
=700, height
=250)
48 self
.input_dir
= input.Input()
49 self
.input_file
= input.Input()
50 self
._current
_sort
= 'alpha'
52 self
.button_ok
= button
.Button("Ouvrir")
53 self
.button_sort_alpha
= button
.Button("A-Z")
54 self
.button_sort_alpha
.connect(CLICK
, self
._set
_current
_sort
_, 'alpha')
55 self
.button_sort_num
= button
.Button("0-9")
56 self
.button_sort_num
.connect(CLICK
, self
._set
_current
_sort
_, 'num')
58 self
.body
.td(basic
.Label("Dossier"), style
=td_style
, align
=-1)
59 self
.body
.td(self
.input_dir
, style
=td_style
)
60 self
.body
.td(self
.button_sort_alpha
)
61 self
.body
.td(self
.button_sort_num
)
63 self
.body
.td(self
.list, colspan
=4, style
=td_style
)
64 self
.list.connect(CHANGE
, self
._item
_select
_changed
_, None)
65 #self.list.connect(CLICK, self._check_dbl_click_, None)
66 self
._last
_time
_click
= pygame
.time
.get_ticks()
67 self
.button_ok
.connect(CLICK
, self
._button
_okay
_clicked
_, None)
69 self
.body
.td(basic
.Label("Fichier"), style
=td_style
, align
=-1)
70 self
.body
.td(self
.input_file
, style
=td_style
)
71 self
.body
.td(self
.button_ok
, style
=td_style
, colspan
=2)
73 Dialog
.__init
__(self
, self
.title
, self
.body
)
77 self
.input_dir
.value
= self
.curdir
78 self
.input_dir
.pos
= len(self
.curdir
)
79 self
.input_dir
.vpos
= 0
83 for i
in os
.listdir(self
.curdir
):
84 if i
.startswith('.') : continue
85 if os
.path
.isdir(os
.path
.join(self
.curdir
, i
)): dirs
.append(i
)
88 self
.input_file
.value
= "Dossier innacessible !"
95 self
.list.add(i
, image
=self
.dir_img
, value
=i
)
99 if not i
.endswith('.xml') :
101 filepath
= os
.path
.join(self
.curdir
, i
)
102 xmlFiles
.append(filepath
)
105 printableLines
= self
.getPrintableLines(xmlFiles
)
106 for l
in printableLines
:
107 imgpath
= os
.path
.splitext(os
.path
.join(self
.curdir
, l
[1]))[0] + '.jpg'
108 if os
.path
.exists(imgpath
) :
109 img
= pygame
.image
.load(imgpath
)
110 iw
, ih
= img
.get_width(), img
.get_height()
113 style
['width'] = PICTURE_ITEM_SIZE
114 style
['height'] = int(round(PICTURE_ITEM_SIZE
* float(ih
) / iw
))
116 style
['heigth'] = PICTURE_ITEM_SIZE
117 style
['width'] = int(round(PICTURE_ITEM_SIZE
* float(iw
) / ih
))
119 img
= basic
.Image(img
, style
=style
)
121 img
= self
.soundfile_img
122 self
.list.add(l
[0], value
= l
[1], image
= img
)
124 self
.list.set_vertical_scroll(0)
126 def getPrintableLines(self
, xmlFiles
) :
127 index
= self
.getUpdatedIndex(xmlFiles
)
133 printableLines
.append(('%s - %s / %s' % (l
[2], l
[3], l
[4]), l
[0]))
135 return printableLines
139 def getSongTitle(file) :
140 it
= ElementTree
.iterparse(file, ['start', 'end'])
142 title
= os
.path
.basename(file)
145 if el
.tag
== 'credit' :
147 if el
.tag
== 'credit-words' and creditFound
:
150 if el
.tag
== 'part-list' :
151 # au delà de ce tag : aucune chance de trouver un titre
156 def getSongMetadata(file) :
158 metadata
['title'] = FileOpenDialog
.getSongTitle(file).encode('iso-8859-1')
159 metadata
['mtime'] = str(os
.stat(file).st_mtime
)
160 metadata
['file'] = os
.path
.basename(file)
161 song
= musicXml2Song(file)
162 metadata
['distinctNotes'] = len(song
.distinctNotes
)
164 histo
= song
.intervalsHistogram
165 coeffInter
= reduce(lambda a
, b
: a
+ b
,
166 [abs(k
) * v
for k
, v
in histo
.items()])
168 totInter
= reduce(lambda a
, b
: a
+b
, histo
.values())
169 totInter
= totInter
- histo
.get(0, 0)
170 difficulty
= int(round(float(coeffInter
) / totInter
, 0))
171 metadata
['difficulty'] = difficulty
175 def getUpdatedIndex(self
, xmlFiles
) :
176 indexTxtPath
= os
.path
.join(self
.curdir
, INDEX_TXT
)
179 if not os
.path
.exists(indexTxtPath
) :
180 musicXmlFound
= False
181 tmp
= tempfile
.TemporaryFile(mode
='r+')
182 for file in xmlFiles
:
184 metadata
= FileOpenDialog
.getSongMetadata(file)
186 except ValueError, e
:
188 if e
.args
and e
.args
[0] == 'not a musicxml file' :
191 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(difficulty)d\n' % metadata
197 indexFile
= open(indexTxtPath
, 'w')
198 indexFile
.write(tmp
.read())
203 indexTxt
= open(indexTxtPath
, 'r')
205 # check if index is up to date, and update entries if so.
206 for l
in filter(None, indexTxt
.readlines()) :
207 parts
= l
.split('\t')
208 fileBaseName
, modificationTime
= parts
[0], parts
[1]
209 filePath
= os
.path
.join(self
.curdir
, fileBaseName
)
211 if not os
.path
.exists(filePath
) :
214 indexedFiles
[fileBaseName
] = l
215 currentMtime
= str(os
.stat(filePath
).st_mtime
)
217 # check modification time missmatch
218 if currentMtime
!= modificationTime
:
220 metadata
= FileOpenDialog
.getSongMetadata(filePath
)
222 except ValueError, e
:
224 if e
.args
and e
.args
[0] == 'not a musicxml file' :
227 metadata
= FileOpenDialog
.getSongMetadata(filePath
)
228 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(difficulty)d\n' % metadata
229 indexedFiles
[fileBaseName
] = line
231 # check for new files.
232 for file in xmlFiles
:
233 fileBaseName
= os
.path
.basename(file)
234 if not indexedFiles
.has_key(fileBaseName
) :
236 metadata
= FileOpenDialog
.getSongMetadata(filePath
)
238 except ValueError, e
:
240 if e
.args
and e
.args
[0] == 'not a musicxml file' :
243 metadata
= FileOpenDialog
.getSongMetadata(file)
244 line
= '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(difficulty)d\n' % metadata
245 indexedFiles
[fileBaseName
] = line
247 # ok, the index is up to date !
249 index
= indexedFiles
.values()
252 if self
._current
_sort
== 'alpha' :
254 da
= desacc(a
.split('\t')[2]).lower()
255 db
= desacc(b
.split('\t')[2]).lower()
258 elif self
._current
_sort
== 'num' :
260 da
= int(a
.split('\t')[3])
261 db
= int(b
.split('\t')[3])
269 def _set_current_sort_(self
, arg
) :
270 self
._current
_sort
= arg
274 def _check_dbl_click_(self
, arg
) :
275 if pygame
.time
.get_ticks() - self
._last
_time
_click
< 300 :
276 self
._button
_okay
_clicked
_(None)
278 self
._last
_time
_click
= pygame
.time
.get_ticks()
281 FileDialog
.event(self
, e
)
283 if e
.type == CLICK
and \
285 self
.list.rect
.collidepoint(e
.pos
) :
286 self
._check
_dbl
_click
_(e
)
288 if e
.type == KEYDOWN
and e
.key
== K_RETURN
:
289 self
._button
_okay
_clicked
_(None)
293 from unicodedata
import decomposition
294 from string
import printable
295 _printable
= dict([(c
, True) for c
in printable
])
296 isPrintable
= _printable
.has_key
298 def _recurseDecomposition(uc
):
299 deco
= decomposition(uc
).split()
304 if code
.startswith('<') :
306 c
= unichr(int(code
, 16))
307 subDeco
= decomposition(c
).split()
316 fullDeco
= u
''.join(filter(lambda c
: isPrintable(c
), fullDeco
))
319 def desacc(s
, encoding
='iso-8859-1') :
320 us
= s
.decode(encoding
, 'ignore')
323 ret
.append(_recurseDecomposition(uc
))