https://gall.dcinside.com/board/view/?id=programming&no=2612215&page=1
파이썬 관련해서 질문좀 하겠습니다...tts 프로그램을 쓰다보면,?-1f1f7; 이렇게 생긴 이모지가 포함된 문장이 있다고 가정한다면,예를들자면,gall.dcinside.com이글 쓴 사람인데,
해당 파일 generator.py 안에 있는 자료 건들면 된다 말해주신분이 있는것같아서... 어느 부분에 어떻게 추가하면, 이모지는 스킵하고 텍스트만 읽어줄까요...?ㅜㅜ
아래는 해당 파일 전체 내용입니다... 글자수 제한있어서 나눠서 올리겠습니다...
# -*- coding: utf-8 -*-
# AwesomeTTS text-to-speech add-on for Anki
# Copyright (C) 2010-Present Anki AwesomeTTS Development Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
File generation dialogs
"""
from re import compile as re
import aqt.qt
from .base import Dialog, ServiceDialog
from .common import Checkbox, Label, Note
__all__ = ['BrowserGenerator', 'EditorGenerator']
class BrowserGenerator(ServiceDialog):
"""
Provides a dialog for generating many media files to multiple cards
from the card browser.
"""
HELP_USAGE_DESC = "Adding audio to multiple notes"
HELP_USAGE_SLUG = 'Batch-Generation'
_RE_WHITESPACE = re(r'\s+')
__slots__ = [
'_browser', # reference to the current Anki browser window
'_notes', # list of Note objects selected when window opened
'_process', # state during processing; see accept() method below
]
def __init__(self, browser, *args, **kwargs):
"""
Sets our title.
"""
self._browser = browser
self._notes = None # set in show()
self._process = None # set in accept()
super(BrowserGenerator, self).__init__(
title="Add TTS Audio to Selected Notes",
*args, **kwargs
)
# UI Construction ########################################################
def _ui_control(self):
"""
Returns the superclass's text and preview buttons, adding our
inputs to control the mass generation process, and then the base
class's cancel/OK buttons.
"""
header = Label("Fields and Handling")
header.setFont(self._FONT_HEADER)
intro = Note() # see show() for where the text is initialized
intro.setObjectName('intro')
layout = super(BrowserGenerator, self)._ui_control()
layout.addWidget(header)
layout.addWidget(intro)
layout.addStretch()
layout.addLayout(self._ui_control_fields())
layout.addWidget(self._ui_control_handling())
layout.addStretch()
layout.addWidget(self._ui_buttons())
return layout
def _ui_control_fields(self):
"""
Returns a grid layout with the source and destination fields.
Note that populating the field dropdowns is deferred to the
show() event handler because the available fields might change
from call to call.
"""
source_label = Label("Source Field:")
source_label.setFont(self._FONT_LABEL)
source_dropdown = aqt.qt.QComboBox()
source_dropdown.setObjectName('source')
dest_label = Label("Destination Field:")
dest_label.setFont(self._FONT_LABEL)
dest_dropdown = aqt.qt.QComboBox()
dest_dropdown.setObjectName('dest')
layout = aqt.qt.QGridLayout()
layout.addWidget(source_label, 0, 0)
layout.addWidget(source_dropdown, 0, 1)
layout.addWidget(dest_label, 1, 0)
layout.addWidget(dest_dropdown, 1, 1)
return layout
def _ui_control_handling(self):
"""
Return the append/overwrite radio buttons and behavior checkbox.
"""
append = aqt.qt.QRadioButton(
"&Append [sound:xxx] Tag onto Destination Field"
)
append.setObjectName('append')
append.toggled.connect(self._on_handling_toggled)
overwrite = aqt.qt.QRadioButton(
"Over&write the Destination Field w/ Media Filename"
)
overwrite.setObjectName('overwrite')
overwrite.toggled.connect(self._on_handling_toggled)
behavior = Checkbox(object_name='behavior')
behavior.stateChanged.connect(
lambda status: self._on_behavior_changed(),
)
layout = aqt.qt.QVBoxLayout()
layout.addWidget(append)
layout.addWidget(overwrite)
layout.addSpacing(self._SPACING)
layout.addWidget(behavior)
widget = aqt.qt.QWidget()
widget.setLayout(layout)
return widget
def _ui_buttons(self):
"""
Adjust title of the OK button.
"""
buttons = super(BrowserGenerator, self)._ui_buttons()
buttons.findChild(aqt.qt.QAbstractButton, 'okay').setText("&Generate")
return buttons
# Events #################################################################
def show(self, *args, **kwargs):
"""
Populate the source and destination dropdowns, recall the
handling and behavior inputs, and focus the source dropdown.
Note that the fields are dumped and repopulated each time,
because the list of fields might change between displays of the
window.
"""
self._notes = [
self._browser.mw.col.getNote(note_id)
for note_id in self._browser.selectedNotes()
]
self.findChild(Note, 'intro').setText(
'%d note%s selected. Click "Help" for usage hints.' %
(len(self._notes), "s" if len(self._notes) != 1 else "")
)
fields = sorted({
field
for note in self._notes
for field in note.keys()
})
config = self._addon.config
source = self.findChild(aqt.qt.QComboBox, 'source')
source.clear()
for field in fields:
source.addItem(field, field)
source.setCurrentIndex(
max(source.findData(config['last_mass_source']), 0)
)
dest = self.findChild(aqt.qt.QComboBox, 'dest')
dest.clear()
for field in fields:
dest.addItem(field, field)
dest.setCurrentIndex(
max(dest.findData(config['last_mass_dest']), 0)
)
self.findChild(
aqt.qt.QRadioButton,
'append' if config['last_mass_append'] else 'overwrite',
).setChecked(True)
self.findChild(Checkbox, 'behavior') \
.setChecked(config['last_mass_behavior'])
super(BrowserGenerator, self).show(*args, **kwargs)
source.setFocus()
def accept(self):
"""
Check to make sure that we have at least one note, pull the
service options, and kick off the processing.
"""
now = self._get_all()
source = now['last_mass_source']
dest = now['last_mass_dest']
append = now['last_mass_append']
behavior = now['last_mass_behavior']
eligible_notes = [
note
for note in self._notes
if source in note and dest in note
]
if not eligible_notes:
self._alerts(
f"Of the {len(self._notes)} notes selected in the browser, "
f"none have both '{source}' and '{dest}' fields."
if len(self._notes) > 1
else f"The selected note does not have both "
f"'{source}' and '{dest}' fields.",
self,
)
return
self._disable_inputs()
svc_id = now['last_service']
options = (None if svc_id.startswith('group:') else
now['last_options'][now['last_service']])
self._process = {
'all': now,
'aborted': False,
'progress': _Progress(
maximum=len(eligible_notes),
on_cancel=self._accept_abort,
title="Generating MP3s",
addon=self._addon,
parent=self,
),
'service': {
'id': svc_id,
'options': options,
},
'fields': {
'source': source,
'dest': dest,
},
'handling': {
'append': append,
'behavior': behavior,
},
'queue': eligible_notes,
'counts': {
'total': len(self._notes),
'elig': len(eligible_notes),
'skip': len(self._notes) - len(eligible_notes),
'done': 0, # all notes processed
'okay': 0, # calls which resulted in a successful MP3
'fail': 0, # calls which resulted in an exception
},
'failednotes': [],
'exceptions': {},
'throttling': {
'calls': {}, # unthrottled download calls made per service
'sleep': self._addon.config['throttle_sleep'],
'threshold': self._addon.config['throttle_threshold'],
},
}
self._browser.mw.checkpoint("AwesomeTTS Batch Update")
self._process['progress'].show()
self._accept_next()
def _accept_abort(self):
"""
Flags that the user has requested that processing stops.
"""
self._process['aborted'] = True
def _accept_next(self):
"""
Pop the next note off the queue, if not throttled, and process.
"""
self._accept_update()
proc = self._process
throttling = proc['throttling']
if proc['aborted'] or not proc['queue']:
self._accept_done()
return
if throttling['calls'] and \
max(throttling['calls'].values()) >= throttling['threshold']:
# at least one service needs a break
timer = aqt.qt.QTimer()
throttling['timer'] = timer
throttling['countdown'] = throttling['sleep']
timer.timeout.connect(self._accept_throttled)
timer.setInterval(1000)
timer.start()
return
note = proc['queue'].pop(0)
phrase = note[proc['fields']['source']]
phrase = self._addon.strip.from_note(phrase)
self._accept_update(phrase)
def done():
"""Count the processed note."""
proc['counts']['done'] += 1
def okay(path):
"""Count the success and update the note."""
filename = self._browser.mw.col.media.addFile(path)
dest = proc['fields']['dest']
note[dest] = self._accept_next_output(note[dest], filename)
proc['counts']['okay'] += 1
note.flush()
def fail(exception, text="Not available by _accept_next.fail"):
"""Count the failure and the unique message."""
proc['counts']['fail'] += 1
proc['failednotes'].append(text)
message = str(exception)
if isinstance(message, str):
message = self._RE_WHITESPACE.sub(' ', message).strip()
try:
proc['exceptions'][message] += 1
except KeyError:
proc['exceptions'][message] = 1
def miss(svc_id, count):
"""Count the cache miss."""
try:
throttling['calls'][svc_id] += count
except KeyError:
throttling['calls'][svc_id] = count
callbacks = dict(
done=done, okay=okay, fail=fail, miss=miss,
# The call to _accept_next() is done via a single-shot QTimer for
# a few reasons: keep the UI responsive, avoid a "maximum
# recursion depth exceeded" exception if we hit a string of cached
# files, and allow time to respond to a "cancel".
then=lambda: aqt.qt.QTimer.singleShot(0, self._accept_next),
)
svc_id = proc['service']['id']
want_human = (self._addon.config['filenames_human'] or '{{text}}' if
self._addon.config['filenames'] == 'human' else False)
if svc_id.startswith('group:'):
config = self._addon.config
self._addon.router.group(text=phrase,
group=config['groups'][svc_id[6:]],
presets=config['presets'],
callbacks=callbacks,
want_human=want_human,
note=note)
else:
self._addon.router(svc_id=svc_id,
text=phrase,
options=proc['service']['options'],
callbacks=callbacks,
want_human=want_human,
note=note)
def _accept_next_output(self, old_value, filename):
"""
Given a note's old value and our current handling options,
returns a new note value using the passed filename.
"""
proc = self._process
if proc['handling']['append']:
if proc['handling']['behavior']:
return self._addon.strip.sounds.univ(old_value).strip() + \
' [sound:%s]' % filename
elif filename in old_value:
return old_value
else:
return old_value + ' [sound:%s]' % filename
else:
if proc['handling']['behavior']:
return '[sound:%s]' % filename
else:
return filename
def _accept_throttled(self):
"""
Called for every "timeout" of the timer during a throttling.
"""
proc = self._process
if proc['aborted']:
proc['throttling']['timer'].stop()
self._accept_done()
return
proc['throttling']['countdown'] -= 1
self._accept_update()
if proc['throttling']['countdown'] <= 0:
proc['throttling']['timer'].stop()
del proc['throttling']['countdown']
del proc['throttling']['timer']
proc['throttling']['calls'] = {}
self._accept_next()
댓글 0