From 334d126237bba450bf6c299cda267113d0cb2108 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 17 Jan 2009 01:05:39 +0900 Subject: [PATCH] recording & noise profile support on linux --- anki/deck.py | 10 ++--- anki/exporting.py | 13 +++--- anki/importing/__init__.py | 14 +++--- anki/importing/anki10.py | 2 +- anki/media.py | 2 +- anki/sound.py | 88 +++++++++++++++++++++++++++++++++++++- 6 files changed, 107 insertions(+), 22 deletions(-) diff --git a/anki/deck.py b/anki/deck.py index 096186d64..9e400fa99 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -1453,8 +1453,8 @@ where id = :id""", pending) # Progress info ########################################################################## - def startProgress(self, title, min, max): - runHook("startProgress", title, min, max) + def startProgress(self, max=100, min=0, title=None): + runHook("startProgress", max, min, title) def updateProgress(self, label=None, value=None): runHook("updateProgress", label, value) @@ -1641,7 +1641,7 @@ Return new path, relative to media dir.""" def fixIntegrity(self): "Responsibility of caller to call rebuildQueue()" - self.startProgress(_("Check DB"), 0, 11) + self.startProgress(11) self.updateProgress(_("Checking integrity...")) if self.s.scalar("pragma integrity_check") != "ok": return _("Database file damaged. Restore from backup.") @@ -1873,8 +1873,8 @@ select sql from undoLog where seq > :s and seq <= :e order by seq desc""", s=start, e=end) mod = len(sql) / 35 if mod: - self.startProgress(_("Undo/Redo"), 0, 36) - self.updateProgress(_("Applying changes...")) + self.startProgress(36) + self.updateProgress(_("Processing...")) newstart = self._latestUndoRow() for c, s in enumerate(sql): if mod and not c % mod: diff --git a/anki/exporting.py b/anki/exporting.py index 2a6f91cb0..ee484d601 100644 --- a/anki/exporting.py +++ b/anki/exporting.py @@ -59,11 +59,11 @@ class AnkiExporter(Exporter): self.includeSchedulingInfo = False def exportInto(self, path): - n = 4 + n = 3 if not self.includeSchedulingInfo: n += 1 - self.deck.startProgress(_("Export"), 0, n) - self.deck.updateProgress(_("Determining items...")) + self.deck.startProgress(n) + self.deck.updateProgress(_("Exporting...")) self.newDeck = DeckStorage.Deck(path) client = SyncClient(self.deck) server = SyncServer(self.newDeck) @@ -74,12 +74,12 @@ class AnkiExporter(Exporter): # set up a custom change list and sync lsum = self.localSummary() rsum = server.summary(0) - self.deck.updateProgress(_("Copying...")) + self.deck.updateProgress() payload = client.genPayload((lsum, rsum)) - self.deck.updateProgress(_("Applying...")) + self.deck.updateProgress() res = server.applyPayload(payload) if not self.includeSchedulingInfo: - self.deck.updateProgress(_("Updating schedule...")) + self.deck.updateProgress() self.newDeck.s.statement(""" delete from reviewHistory""") self.newDeck.s.statement(""" @@ -121,7 +121,6 @@ delete from stats""") bulkClient.server = bulkServer bulkClient.sync() # need to save manually - self.deck.updateProgress(_("Finalizing...")) self.newDeck.rebuildCounts() self.exportedCards = self.newDeck.cardCount self.newDeck.s.commit() diff --git a/anki/importing/__init__.py b/anki/importing/__init__.py index 5fb489116..93dc18ad8 100644 --- a/anki/importing/__init__.py +++ b/anki/importing/__init__.py @@ -46,11 +46,11 @@ class Importer(object): def doImport(self): "Import." - self.deck.startProgress(_("Import"), 0, 6) - self.deck.updateProgress(_("Reading source...")) + self.deck.startProgress(6) + self.deck.updateProgress(_("Importing...")) c = self.foreignCards() self.importCards(c) - self.deck.updateProgress(_("Updating priorities...")) + self.deck.updateProgress() self.deck.updateAllPriorities() self.deck.finishProgress() if c: @@ -123,7 +123,7 @@ all but one card template.""")) def addCards(self, cards): "Add facts in bulk from foreign cards." # add facts - self.deck.updateProgress(_("Adding facts...")) + self.deck.updateProgress() factIds = [genID() for n in range(len(cards))] self.deck.s.execute(factsTable.insert(), [{'modelId': self.model.id, @@ -134,7 +134,7 @@ all but one card template.""")) delete from factsDeleted where factId in (%s)""" % ",".join([str(s) for s in factIds])) # add all the fields - self.deck.updateProgress(_("Adding fields...")) + self.deck.updateProgress() for fm in self.model.fieldModels: try: index = self.mapping.index(fm) @@ -150,7 +150,7 @@ where factId in (%s)""" % ",".join([str(s) for s in factIds])) self.deck.s.execute(fieldsTable.insert(), data) # and cards - self.deck.updateProgress(_("Adding cards...")) + self.deck.updateProgress() now = time.time() for cm in self.model.cardModels: self._now = now @@ -165,7 +165,7 @@ where factId in (%s)""" % ",".join([str(s) for s in factIds])) 'type': 2},cards[m]) for m in range(len(cards))] self.deck.s.execute(cardsTable.insert(), data) - self.deck.updateProgress(_("Caching QA...")) + self.deck.updateProgress() self.deck.updateCardsFromModel(self.model) self.deck.cardCount += len(cards) self.total = len(factIds) diff --git a/anki/importing/anki10.py b/anki/importing/anki10.py index fa2a1ca69..78889159c 100644 --- a/anki/importing/anki10.py +++ b/anki/importing/anki10.py @@ -18,7 +18,7 @@ class Anki10Importer(Importer): def doImport(self): "Import." - self.deck.startProgress(_("Import"), 0, 4) + self.deck.startProgress(4) self.deck.updateProgress(_("Importing...")) src = DeckStorage.Deck(self.file) client = SyncClient(self.deck) diff --git a/anki/media.py b/anki/media.py index 4d2655ec8..8125caa31 100644 --- a/anki/media.py +++ b/anki/media.py @@ -119,7 +119,7 @@ def rebuildMediaDir(deck, deleteRefs=False, dirty=True): unusedFileCount = 0 missingFileCount = 0 deck.mediaDir(create=True) - deck.startProgress(_("Check Media DB"), 0, 16) + deck.startProgress(16, 0, _("Check Media DB")) # rename all files to checksum versions, note non-renamed ones deck.updateProgress(_("Checksum files...")) files = os.listdir(unicode(deck.mediaDir())) diff --git a/anki/sound.py b/anki/sound.py index 99a01fcb9..f927e6c56 100644 --- a/anki/sound.py +++ b/anki/sound.py @@ -8,7 +8,7 @@ Sound support """ __docformat__ = 'restructuredtext' -import re, sys, threading, time, subprocess, os +import re, sys, threading, time, subprocess, os, signal # Shared utils ########################################################################## @@ -26,6 +26,30 @@ def hasSound(text): # External audio ########################################################################## +# the amount of noise to cancel +NOISE_AMOUNT = "0.1" +# the amount of amplification +NORM_AMOUNT = "-3" +# the amount of bass +BASS_AMOUNT = "+0" +# the amount to fade at end +FADE_AMOUNT = "0.2" + +noiseProfile = "" + +processingSrc = "tmp.wav" +processingDst = "tmp.mp3" +processingChain = [] +tmpFiles = ["tmp2.wav", "tmp3.wav"] + +cmd = ["sox", processingSrc, "tmp2.wav"] +processingChain = [ + None, # placeholder + ["sox", "tmp2.wav", "tmp3.wav", "norm", NORM_AMOUNT, + "bass", BASS_AMOUNT, "fade", FADE_AMOUNT, "0"], + ["lame", "tmp3.wav", processingDst, "--noreplaygain"], + ] + queue = [] manager = None @@ -33,8 +57,11 @@ if sys.platform.startswith("win32"): base = os.path.join(os.path.dirname(sys.argv[0]), "mplayer.exe") #base = "C:\mplayer.exe" externalPlayer = [base, "-ao", "win32", "-really-quiet"] + externalRecorder = ["rec", processingSrc] else: externalPlayer = ["mplayer", "-really-quiet"] + externalRecorder = ["ecasound", "-x", "-f:16,1,44100", "-i", + "alsahw,1,0", "-o", processingSrc] # don't show box on windows if sys.platform == "win32": @@ -43,6 +70,29 @@ if sys.platform == "win32": else: si = None +# noise profiles +########################################################################## + +def checkForNoiseProfile(): + cmd = ["sox", processingSrc, "tmp2.wav"] + if os.path.exists(noiseProfile): + cmd = cmd + ["noisered", noiseProfile, NOISE_AMOUNT] + processingChain[0] = cmd + +def generateNoiseProfile(file): + try: + os.unlink(noiseProfile) + except OSError: + pass + subprocess.Popen(["sox", processingSrc, tmpFiles[0], "trim", "1.5", "1.5"]) + subprocess.Popen(["sox", tmpFiles[0], tmpFiles[1], + "noiseprof", noiseProfile]).wait() + processingChain[0] = ["sox", processingSrc, "tmp2.wav", + "noisered", noiseProfile, NOISE_AMOUNT] + +# External playing +########################################################################## + class QueueMonitor(threading.Thread): def run(self): @@ -68,6 +118,37 @@ def clearQueueExternal(): global queue queue = [] +# External recording +########################################################################## + +class _Recorder(object): + + def postprocess(self): + for c in processingChain: + print c + if subprocess.Popen(c, startupinfo=si).wait(): + raise Exception("problem with" + str(c)) + +class ExternalUnixRecorder(_Recorder): + + def __init__(self): + for t in tmpFiles + [processingSrc, processingDst]: + try: + os.unlink(t) + except OSError: + pass + + def start(self): + self.proc = subprocess.Popen( + externalRecorder, startupinfo=si) + + def stop(self): + os.kill(self.proc.pid, signal.SIGINT) + self.proc.wait() + + def file(self): + return processingDst + # Mac audio support ########################################################################## @@ -124,6 +205,11 @@ except ImportError: if sys.platform.startswith("darwin"): play = playOSX clearAudioQueue = clearQueueOSX + Recorder = None else: play = playExternal clearAudioQueue = clearQueueExternal + if sys.platform.startswith("win32"): + Recorder = None + else: + Recorder = ExternalUnixRecorder