예제 #1
0
    def requestStarted(self, job):
        """Handle a request for a qute: scheme.

        This method must be reimplemented by all custom URL scheme handlers.
        The request is asynchronous and does not need to be handled right away.

        Args:
            job: QWebEngineUrlRequestJob
        """
        url = job.requestUrl()

        if url.scheme() in ['chrome-error', 'chrome-extension']:
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
            job.fail(QWebEngineUrlRequestJob.UrlInvalid)
            return

        if not self._check_initiator(job):
            return

        if job.requestMethod() != b'GET':
            job.fail(QWebEngineUrlRequestJob.RequestDenied)
            return

        assert url.scheme() == 'qute'

        log.misc.debug("Got request for {}".format(url.toDisplayString()))
        try:
            mimetype, data = qutescheme.data_for_url(url)
        except qutescheme.Error as e:
            errors = {
                qutescheme.NotFoundError:
                    QWebEngineUrlRequestJob.UrlNotFound,
                qutescheme.UrlInvalidError:
                    QWebEngineUrlRequestJob.UrlInvalid,
                qutescheme.RequestDeniedError:
                    QWebEngineUrlRequestJob.RequestDenied,
                qutescheme.SchemeOSError:
                    QWebEngineUrlRequestJob.UrlNotFound,
                qutescheme.Error:
                    QWebEngineUrlRequestJob.RequestFailed,
            }
            exctype = type(e)
            log.misc.error("{} while handling qute://* URL".format(
                exctype.__name__))
            job.fail(errors[exctype])
        except qutescheme.Redirect as e:
            qtutils.ensure_valid(e.url)
            job.redirect(e.url)
        else:
            log.misc.debug("Returning {} data".format(mimetype))

            # We can't just use the QBuffer constructor taking a QByteArray,
            # because that somehow segfaults...
            # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
            buf = QBuffer(parent=self)
            buf.open(QIODevice.WriteOnly)
            buf.write(data)
            buf.seek(0)
            buf.close()
            job.reply(mimetype.encode('ascii'), buf)
예제 #2
0
    def requestStarted(self, job: QWebEngineUrlRequestJob) -> None:
        path = job.requestUrl().path()
        path = os.path.realpath(path)

        print(path)

        if not path:
            job.fail(QWebEngineUrlRequestJob.UrlInvalid)
            return

        if not os.path.exists(path):
            job.fail(QWebEngineUrlRequestJob.UrlNotFound)
            return

        try:
            with open(path, 'rb') as file:
                content_type = mimetypes.guess_type(path)
                buffer = QBuffer(parent=self)

                buffer.open(QIODevice.WriteOnly)
                buffer.write(file.read())
                buffer.seek(0)
                buffer.close()

                job.reply(content_type[0].encode(), buffer)

        except Exception as err:
            raise err
예제 #3
0
    def requestStarted(self, job: QWebEngineUrlRequestJob):
        url = job.requestUrl()
        scheme = url.scheme()
        mime = "text/html"
        data = b""

        if scheme == "dict":
            try:
                path = url.path().split("#", 1)[0]
                (data, mime) = self._ldoce5.get_content(path)
                mime = "text/html"
            except NotFoundError:
                data = b"<h2>Content Not Found</h2>"
                mime = "text/html"
            except FilemapError:
                data = b"<h2>File-Location Map Not Available</h2>"
                mime = "text/html"
            except ArchiveError:
                data = b"<h2>Dictionary Data Not Available</h2>"
                mime = "text/html"
        elif scheme == "static":
            try:
                data = _load_static_data(url.path().lstrip("/"))
                mime = ""
            except EnvironmentError:
                data = b"<h2>Static File Not Found</h2>"
                mime = "text/html"
        elif scheme == "search":
            searcher_hp = self._searcher_hp
            searcher_de = self._searcher_de
            if searcher_hp and searcher_de:
                try:
                    data = enc_utf8(
                        search_and_render(url, searcher_hp, searcher_de))
                    mime = "text/html"
                except Exception:
                    s = u"<h2>Error</h2><div>{0}</div>".format("<br>".join(
                        traceback.format_exc().splitlines()))
                    data = enc_utf8(s)
                    mime = "text/html"
            else:
                mime = "text/html"
                data = b"<p>The full-text search index has not been created yet or broken.</p>"
        else:
            job.fail(QWebEngineUrlRequestJob.Error.RequestAborted)

        buffer = QBuffer(job)
        buffer.open(QBuffer.OpenModeFlag.ReadWrite)
        buffer.write(data or b"")
        buffer.seek(0)
        job.reply(mime.encode("ascii"), buffer)
예제 #4
0
 def sendDeleteRequest(self, endpoint, data={}, params={}, headers={}):
     buff = QBuffer()
     buff.open(QBuffer.ReadWrite)
     d = json.dumps(data).encode('utf-8')
     buff.write(d)
     buff.seek(0)
     headers.update({"Content-Type":"application/json"})
     content = self.sendRequest( endpoint, params,
         'delete',
         buff,
         headers=headers
     )
     buff.close()
     return content
예제 #5
0
    def requestStarted(self, job):
        """Handle a request for a qute: scheme.

        This method must be reimplemented by all custom URL scheme handlers.
        The request is asynchronous and does not need to be handled right away.

        Args:
            job: QWebEngineUrlRequestJob
        """
        url = job.requestUrl()

        if url.scheme() == 'chrome-error':
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
            job.fail(QWebEngineUrlRequestJob.UrlInvalid)
            return

        assert job.requestMethod() == b'GET'
        assert url.scheme() == 'qute'
        log.misc.debug("Got request for {}".format(url.toDisplayString()))
        try:
            mimetype, data = qutescheme.data_for_url(url)
        except qutescheme.NoHandlerFound:
            log.misc.debug("No handler found for {}".format(
                url.toDisplayString()))
            job.fail(QWebEngineUrlRequestJob.UrlNotFound)
        except qutescheme.QuteSchemeOSError:
            # FIXME:qtwebengine how do we show a better error here?
            log.misc.exception("OSError while handling qute://* URL")
            job.fail(QWebEngineUrlRequestJob.UrlNotFound)
        except qutescheme.QuteSchemeError:
            # FIXME:qtwebengine how do we show a better error here?
            log.misc.exception("Error while handling qute://* URL")
            job.fail(QWebEngineUrlRequestJob.RequestFailed)
        except qutescheme.Redirect as e:
            qtutils.ensure_valid(e.url)
            job.redirect(e.url)
        else:
            log.misc.debug("Returning {} data".format(mimetype))

            # We can't just use the QBuffer constructor taking a QByteArray,
            # because that somehow segfaults...
            # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
            buf = QBuffer(parent=self)
            buf.open(QIODevice.WriteOnly)
            buf.write(data)
            buf.seek(0)
            buf.close()
            job.reply(mimetype.encode('ascii'), buf)
    def _resizeImage(self, virtual_path: str, width: int, height: int) -> IO[bytes]:
        input = self.getStream(virtual_path)
        try:
            from PyQt5.QtGui import QImage
            from PyQt5.QtCore import Qt, QBuffer

            image = QImage()
            image.loadFromData(input.read())
            image = image.scaled(width, height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
            output_buffer = QBuffer()
            output_buffer.open(QBuffer.ReadWrite)
            image.save(output_buffer, "PNG")
            output_buffer.seek(0)  # Reset that buffer so that the next guy can request it.
            return BytesIO(output_buffer.readAll())
        except ImportError:
            # TODO: Try other image loaders.
            raise  # Raise import error again if we find no other image loaders.
예제 #7
0
def image_section(section_e: "ImageSection") -> Tuple[str, str]:
    section = section_e.to_dict()
    image = create_images_with_annotation(section)
    width, height = get_image_size(image)
    buf = QBuffer()
    buf.open(QBuffer.ReadWrite)
    image.save(buf, "PNG")
    buf.seek(0)
    content = buf.readAll().toBase64()
    buf.close()
    res = f"""<text:p text:style-name="Standard">
<draw:frame draw:style-name="fr1" draw:name="{uuid.uuid4().hex}" text:anchor-type="paragraph" svg:width="{int(width)}mm"  svg:height="{int(height)}mm" draw:z-index="0">
    <draw:image loext:mime-type="image/png">
<office:binary-data>{content.data().decode()}</office:binary-data>
            </draw:image>
        </draw:frame>
    </text:p>"""
    return res, ""
예제 #8
0
    def requestStarted(self, job):
        """Handle a request for a qute: scheme.

        This method must be reimplemented by all custom URL scheme handlers.
        The request is asynchronous and does not need to be handled right away.

        Args:
            job: QWebEngineUrlRequestJob
        """
        url = job.requestUrl()
        assert job.requestMethod() == b'GET'
        assert url.scheme() == 'qute'
        log.misc.debug("Got request for {}".format(url.toDisplayString()))
        try:
            mimetype, data = qutescheme.data_for_url(url)
        except qutescheme.NoHandlerFound:
            log.misc.debug("No handler found for {}".format(
                url.toDisplayString()))
            job.fail(QWebEngineUrlRequestJob.UrlNotFound)
        except qutescheme.QuteSchemeOSError:
            # FIXME:qtwebengine how do we show a better error here?
            log.misc.exception("OSError while handling qute://* URL")
            job.fail(QWebEngineUrlRequestJob.UrlNotFound)
        except qutescheme.QuteSchemeError:
            # FIXME:qtwebengine how do we show a better error here?
            log.misc.exception("Error while handling qute://* URL")
            job.fail(QWebEngineUrlRequestJob.RequestFailed)
        except qutescheme.Redirect as e:
            qtutils.ensure_valid(e.url)
            job.redirect(e.url)
        else:
            log.misc.debug("Returning {} data".format(mimetype))

            # We can't just use the QBuffer constructor taking a QByteArray,
            # because that somehow segfaults...
            # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
            buf = QBuffer(parent=self)
            buf.open(QIODevice.WriteOnly)
            buf.write(data)
            buf.seek(0)
            buf.close()
            job.reply(mimetype.encode('ascii'), buf)
예제 #9
0
    def requestStarted(self, job: QWebEngineUrlRequestJob) -> None:
        # pylint: disable=invalid-name,missing-function-docstring
        path = job.requestUrl().path()
        path = os.path.realpath(path)

        # print("PATH", job.requestUrl().path())

        if not path:
            # print("JOB FAIL", path)
            job.fail(QWebEngineUrlRequestJob.UrlInvalid)
            return

        if not os.path.exists(path):
            # print("NOT FOUND", path)
            job.fail(QWebEngineUrlRequestJob.UrlNotFound)
            return

        try:
            with open(path, 'rb') as file:
                content_type = mimetypes.guess_type(path)
                if content_type[0] is None:
                    content_type = ("text/plain", None)
                buffer = QBuffer(parent=self)

                buffer.open(QIODevice.WriteOnly)
                buffer.write(file.read())
                buffer.seek(0)
                buffer.close()

                if content_type[0] is None:
                    job.reply("text/plain", "")
                else:
                    job.reply(content_type[0].encode(), buffer)

        except Exception as err:
            raise err
예제 #10
0
    def edit_paste_as(self):
        """Pastes clipboard into one cell using a user specified mime type"""

        grid = self.main_window.grid
        model = grid.model

        # The mimetypes that are supported by pyspread
        mimetypes = ("image", "text/html", "text/plain")
        clipboard = QApplication.clipboard()
        formats = clipboard.mimeData().formats()

        items = [fmt for fmt in formats if any(m in fmt for m in mimetypes)]
        if not items:
            return
        elif len(items) == 1:
            item = items[0]
        else:
            item, ok = QInputDialog.getItem(self.main_window, "Paste as",
                                            "Choose mime type", items,
                                            current=0, editable=False)
            if not ok:
                return

        row, column, table = current = grid.current  # Target cell key

        description_tpl = "Paste {} from clipboard into cell {}"
        description = description_tpl.format(item, current)

        index = model.index(row, column, QModelIndex())

        mime_data = clipboard.mimeData()

        if item == "image/svg+xml":
            # SVG Image
            if mime_data:
                svg = mime_data.data("image/svg+xml")
                self._paste_svg(str(svg, encoding='utf-8'), index)

        elif "image" in item and mime_data.hasImage():
            # Bitmap Image
            image = clipboard.image()
            buffer = QBuffer()
            buffer.open(QBuffer.ReadWrite)
            image.save(buffer, "PNG")
            buffer.seek(0)
            image_data = buffer.readAll()
            buffer.close()
            self._paste_image(image_data, index)

        elif item == "text/html" and mime_data.hasHtml():
            # HTML content
            html = mime_data.html()
            command = CommandSetCellCode(html, model, index, description)
            self.main_window.undo_stack.push(command)
            grid.on_markup_renderer_pressed(True)

        elif item == "text/plain":
            # Normal code
            code = clipboard.text()
            if code:
                command = CommandSetCellCode(code, model, index, description)
                self.main_window.undo_stack.push(command)

        else:
            # Unknown mime type
            return NotImplemented
예제 #11
0
class ControllableAudio(QAudioOutput):
    # This links all the PyQt5 audio playback things -
    # QAudioOutput, QFile, and input from main interfaces

    def __init__(self, format):
        super(ControllableAudio, self).__init__(format)
        # on this notify, move slider (connected in main file)
        self.setNotifyInterval(30)
        self.stateChanged.connect(self.endListener)
        self.tempin = QBuffer()
        self.startpos = 0
        self.timeoffset = 0
        self.keepSlider = False
        #self.format = format
        # set small buffer (10 ms) and use processed time
        self.setBufferSize(
            int(self.format().sampleSize() * self.format().sampleRate() / 100 *
                self.format().channelCount()))

    def isPlaying(self):
        return (self.state() == QAudio.ActiveState)

    def endListener(self):
        # this should only be called if there's some misalignment between GUI and Audio
        if self.state() == QAudio.IdleState:
            # give some time for GUI to catch up and stop
            sleepCycles = 0
            while (self.state() != QAudio.StoppedState and sleepCycles < 30):
                sleep(0.03)
                sleepCycles += 1
                # This loop stops when timeoffset+processedtime > designated stop position.
                # By adding this offset, we ensure the loop stops even if
                # processed audio timer breaks somehow.
                self.timeoffset += 30
                self.notify.emit()
            self.pressedStop()

    def pressedPlay(self, resetPause=False, start=0, stop=0, audiodata=None):
        if not resetPause and self.state() == QAudio.SuspendedState:
            print("Resuming at: %d" % self.pauseoffset)
            self.sttime = time.time() - self.pauseoffset / 1000
            self.resume()
        else:
            if not self.keepSlider or resetPause:
                self.pressedStop()

            print("Starting at: %d" % self.tempin.pos())
            sleep(0.2)
            # in case bar was moved under pause, we need this:
            pos = self.tempin.pos()  # bytes
            pos = self.format().durationForBytes(pos) / 1000  # convert to ms
            pos = pos + start
            print("Pos: %d start: %d stop %d" % (pos, start, stop))
            self.filterSeg(pos, stop, audiodata)

    def pressedPause(self):
        self.keepSlider = True  # a flag to avoid jumping the slider back to 0
        pos = self.tempin.pos()  # bytes
        pos = self.format().durationForBytes(pos) / 1000  # convert to ms
        # store offset, relative to the start of played segment
        self.pauseoffset = pos + self.timeoffset
        self.suspend()

    def pressedStop(self):
        # stop and reset to window/segment start
        self.keepSlider = False
        self.stop()
        if self.tempin.isOpen():
            self.tempin.close()

    def filterBand(self, start, stop, low, high, audiodata, sp):
        # takes start-end in ms, relative to file start
        self.timeoffset = max(0, start)
        start = max(0, start * self.format().sampleRate() // 1000)
        stop = min(stop * self.format().sampleRate() // 1000, len(audiodata))
        segment = audiodata[int(start):int(stop)]
        segment = sp.bandpassFilter(segment,
                                    sampleRate=None,
                                    start=low,
                                    end=high)
        # segment = self.sp.ButterworthBandpass(segment, self.sampleRate, bottom, top,order=5)
        self.loadArray(segment)

    def filterSeg(self, start, stop, audiodata):
        # takes start-end in ms
        self.timeoffset = max(0, start)
        start = max(0, int(start * self.format().sampleRate() // 1000))
        stop = min(int(stop * self.format().sampleRate() // 1000),
                   len(audiodata))
        segment = audiodata[start:stop]
        self.loadArray(segment)

    def loadArray(self, audiodata):
        # loads an array from memory into an audio buffer
        if self.format().sampleSize() == 16:
            audiodata = audiodata.astype(
                'int16')  # 16 corresponds to sampwidth=2
        elif self.format().sampleSize() == 32:
            audiodata = audiodata.astype('int32')
        elif self.format().sampleSize() == 24:
            audiodata = audiodata.astype('int32')
            print("Warning: 24-bit sample playback currently not supported")
        elif self.format().sampleSize() == 8:
            audiodata = audiodata.astype('uint8')
        else:
            print("ERROR: sampleSize %d not supported" %
                  self.format().sampleSize())
            return
        # double mono sound to get two channels - simplifies reading
        if self.format().channelCount() == 2:
            audiodata = np.column_stack((audiodata, audiodata))

        # write filtered output to a BytesIO buffer
        self.tempout = io.BytesIO()
        # NOTE: scale=None rescales using data minimum/max. This can cause clipping. Use scale="none" if this causes weird playback sound issues.
        # in particular for 8bit samples, we need more scaling:
        if self.format().sampleSize() == 8:
            scale = (audiodata.min() / 2, audiodata.max() * 2)
        else:
            scale = None
        wavio.write(self.tempout,
                    audiodata,
                    self.format().sampleRate(),
                    scale=scale,
                    sampwidth=self.format().sampleSize() // 8)

        # copy BytesIO@write to QBuffer@read for playing
        self.temparr = QByteArray(self.tempout.getvalue()[44:])
        # self.tempout.close()
        if self.tempin.isOpen():
            self.tempin.close()
        self.tempin.setBuffer(self.temparr)
        self.tempin.open(QIODevice.ReadOnly)

        # actual timer is launched here, with time offset set asynchronously
        sleep(0.2)
        self.sttime = time.time() - self.timeoffset / 1000
        self.start(self.tempin)

    def seekToMs(self, ms, start):
        print("Seeking to %d ms" % ms)
        # start is an offset for the current view start, as it is position 0 in extracted file
        self.reset()
        self.tempin.seek(self.format().bytesForDuration((ms - start) * 1000))
        self.timeoffset = ms

    def applyVolSlider(self, value):
        # passes UI volume nonlinearly
        # value = QAudio.convertVolume(value / 100, QAudio.LogarithmicVolumeScale, QAudio.LinearVolumeScale)
        value = (math.exp(value / 50) - 1) / (math.exp(2) - 1)
        self.setVolume(value)
예제 #12
0
class HexEditChunks(object):
    """
    Class implementing the storage backend for the hex editor.
    
    When HexEditWidget loads data, HexEditChunks access them using a QIODevice
    interface. When the app uses a QByteArray or Python bytearray interface,
    QBuffer is used to provide again a QIODevice like interface. No data will
    be changed, therefore HexEditChunks opens the QIODevice in
    QIODevice.ReadOnly mode. After every access HexEditChunks closes the
    QIODevice. That's why external applications can overwrite files while
    HexEditWidget shows them.

    When the the user starts to edit the data, HexEditChunks creates a local
    copy of a chunk of data (4 kilobytes) and notes all changes there. Parallel
    to that chunk, there is a second chunk, which keeps track of which bytes
    are changed and which are not.
    """
    BUFFER_SIZE = 0x10000
    CHUNK_SIZE = 0x1000
    READ_CHUNK_MASK = 0xfffffffffffff000

    def __init__(self, ioDevice=None):
        """
        Constructor
        
        @param ioDevice io device to get the data from
        @type QIODevice
        """
        self.__ioDevice = None
        self.__pos = 0
        self.__size = 0
        self.__chunks = []

        if ioDevice is None:
            buf = QBuffer()
            self.setIODevice(buf)
        else:
            self.setIODevice(ioDevice)

    def setIODevice(self, ioDevice):
        """
        Public method to set an io device to read the binary data from.
        
        @param ioDevice io device to get the data from
        @type QIODevice
        @return flag indicating successful operation
        @rtype bool
        """
        self.__ioDevice = ioDevice
        ok = self.__ioDevice.open(QIODevice.ReadOnly)
        if ok:
            # open successfully
            self.__size = self.__ioDevice.size()
            self.__ioDevice.close()
        else:
            # fallback is an empty buffer
            self.__ioDevice = QBuffer()
            self.__size = 0

        self.__chunks = []
        self.__pos = 0

        return ok

    def data(self, pos=0, maxSize=-1, highlighted=None):
        """
        Public method to get data out of the chunks.
        
        @param pos position to get bytes from
        @type int
        @param maxSize maximum amount of bytes to get
        @type int
        @param highlighted reference to a byte array storing highlighting info
        @type bytearray
        @return retrieved data
        @rtype bytearray
        """
        ioDelta = 0
        chunkIdx = 0

        chunk = HexEditChunk()
        buffer = bytearray()

        if highlighted is not None:
            del highlighted[:]

        if pos >= self.__size:
            return buffer

        if maxSize < 0:
            maxSize = self.__size
        elif (pos + maxSize) > self.__size:
            maxSize = self.__size - pos

        self.__ioDevice.open(QIODevice.ReadOnly)

        while maxSize > 0:
            chunk.absPos = sys.maxsize
            chunksLoopOngoing = True
            while chunkIdx < len(self.__chunks) and chunksLoopOngoing:
                # In this section, we track changes before our required data
                # and we take the edited data, if availible. ioDelta is a
                # difference counter to justify the read pointer to the
                # original data, if data in between was deleted or inserted.

                chunk = self.__chunks[chunkIdx]
                if chunk.absPos > pos:
                    chunksLoopOngoing = False
                else:
                    chunkIdx += 1
                    chunkOfs = pos - chunk.absPos
                    if maxSize > (len(chunk.data) - chunkOfs):
                        count = len(chunk.data) - chunkOfs
                        ioDelta += self.CHUNK_SIZE - len(chunk.data)
                    else:
                        count = maxSize
                    if count > 0:
                        buffer += chunk.data[chunkOfs:chunkOfs + count]
                        maxSize -= count
                        pos += count
                        if highlighted is not None:
                            highlighted += chunk.dataChanged[
                                chunkOfs:chunkOfs + count]

            if maxSize > 0 and pos < chunk.absPos:
                # In this section, we read data from the original source. This
                # will only happen, when no copied data is available.
                if chunk.absPos - pos > maxSize:
                    byteCount = maxSize
                else:
                    byteCount = chunk.absPos - pos

                maxSize -= byteCount
                self.__ioDevice.seek(pos + ioDelta)
                readBuffer = bytearray(self.__ioDevice.read(byteCount))
                buffer += readBuffer
                if highlighted is not None:
                    highlighted += bytearray(len(readBuffer))
                pos += len(readBuffer)

        self.__ioDevice.close()
        return buffer

    def write(self, ioDevice, pos=0, count=-1):
        """
        Public method to write data to an io device.
        
        @param ioDevice io device to write the data to
        @type QIODevice
        @param pos position to write bytes from
        @type int
        @param count amount of bytes to write
        @type int
        @return flag indicating success
        @rtype bool
        """
        if count == -1:
            # write all data
            count = self.__size

        ok = ioDevice.open(QIODevice.WriteOnly)
        if ok:
            idx = pos
            while idx < count:
                data = self.data(idx, self.BUFFER_SIZE)
                ioDevice.write(QByteArray(data))

                # increment loop variable
                idx += self.BUFFER_SIZE

            ioDevice.close()

        return ok

    def setDataChanged(self, pos, dataChanged):
        """
        Public method to set highlighting info.
        
        @param pos position to set highlighting info for
        @type int
        @param dataChanged flag indicating changed data
        @type bool
        """
        if pos < 0 or pos >= self.__size:
            # position is out of range, do nothing
            return
        chunkIdx = self.__getChunkIndex(pos)
        posInChunk = pos - self.__chunks[chunkIdx].absPos
        self.__chunks[chunkIdx].dataChanged[posInChunk] = int(dataChanged)

    def dataChanged(self, pos):
        """
        Public method to test, if some data was changed.
        
        @param pos byte position to check
        @type int
        @return flag indicating the changed state
        @rtype bool
        """
        highlighted = bytearray()
        self.data(pos, 1, highlighted)
        return highlighted and bool(highlighted[0])

    def indexOf(self, byteArray, start):
        """
        Public method to search the first occurrence of some data.
        
        @param byteArray data to search for
        @type bytearray
        @param start position to start the search at
        @type int
        @return position the data was found at or -1 if nothing could be found
        @rtype int
        """
        ba = bytearray(byteArray)

        result = -1
        pos = start
        while pos < self.__size:
            buffer = self.data(pos, self.BUFFER_SIZE + len(ba) - 1)
            findPos = buffer.find(ba)
            if findPos >= 0:
                result = pos + findPos
                break

            # increment loop variable
            pos += self.BUFFER_SIZE

        return result

    def lastIndexOf(self, byteArray, start):
        """
        Public method to search the last occurrence of some data.
        
        @param byteArray data to search for
        @type bytearray
        @param start position to start the search at
        @type int
        @return position the data was found at or -1 if nothing could be found
        @rtype int
        """
        ba = bytearray(byteArray)

        result = -1
        pos = start
        while pos > 0 and result < 0:
            sPos = pos - self.BUFFER_SIZE - len(ba) + 1
            if sPos < 0:
                sPos = 0

            buffer = self.data(sPos, pos - sPos)
            findPos = buffer.rfind(ba)
            if findPos >= 0:
                result = sPos + findPos
                break

            # increment loop variable
            pos -= self.BUFFER_SIZE

        return result

    def insert(self, pos, data):
        """
        Public method to insert a byte.
        
        @param pos position to insert at
        @type int
        @param data byte to insert
        @type int (range 0 to 255)
        @return flag indicating success
        @rtype bool
        """
        if pos < 0 or pos > self.__size:
            # position is out of range, do nothing
            return False

        if pos == self.__size:
            chunkIdx = self.__getChunkIndex(pos - 1)
        else:
            chunkIdx = self.__getChunkIndex(pos)
        chunk = self.__chunks[chunkIdx]
        posInChunk = pos - chunk.absPos
        chunk.data.insert(posInChunk, data)
        chunk.dataChanged.insert(posInChunk, 1)
        for idx in range(chunkIdx + 1, len(self.__chunks)):
            self.__chunks[idx].absPos += 1
        self.__size += 1
        self.__pos = pos
        return True

    def overwrite(self, pos, data):
        """
        Public method to overwrite a byte.
        
        @param pos position to overwrite
        @type int
        @param data byte to overwrite with
        @type int (range 0 to 255)
        @return flag indicating success
        @rtype bool
        """
        if pos < 0 or pos >= self.__size:
            # position is out of range, do nothing
            return False

        chunkIdx = self.__getChunkIndex(pos)
        chunk = self.__chunks[chunkIdx]
        posInChunk = pos - chunk.absPos
        chunk.data[posInChunk] = data
        chunk.dataChanged[posInChunk] = 1
        self.__pos = pos
        return True

    def removeAt(self, pos):
        """
        Public method to remove a byte.
        
        @param pos position to remove
        @type int
        @return flag indicating success
        @rtype bool
        """
        if pos < 0 or pos >= self.__size:
            # position is out of range, do nothing
            return False

        chunkIdx = self.__getChunkIndex(pos)
        chunk = self.__chunks[chunkIdx]
        posInChunk = pos - chunk.absPos
        chunk.data.pop(posInChunk)
        chunk.dataChanged.pop(posInChunk)
        for idx in range(chunkIdx + 1, len(self.__chunks)):
            self.__chunks[idx].absPos -= 1
        self.__size -= 1
        self.__pos = pos
        return True

    def __getitem__(self, pos):
        """
        Special method to get a byte at a position.
        
        Note: This realizes the [] get operator.
        
        @param pos position of byte to get
        @type int
        @return requested byte
        @rtype int (range 0 to 255)
        """
        if pos >= self.__size:
            return 0
##            raise IndexError

        return self.data(pos, 1)[0]

    def pos(self):
        """
        Public method to get the current position.
        
        @return current position
        @rtype int
        """
        return self.__pos

    def size(self):
        """
        Public method to get the current data size.
        
        @return current data size
        @rtype int
        """
        return self.__size

    def __getChunkIndex(self, absPos):
        """
        Private method to get the chunk index for a position.
        
        This method checks, if there is already a copied chunk available. If
        there is one, it returns its index. If there is no copied chunk
        available, original data will be copied into a new chunk.
        
        @param absPos absolute position of the data.
        @type int
        @return index of the chunk containing the position
        @rtype int
        """
        foundIdx = -1
        insertIdx = 0
        ioDelta = 0

        for idx in range(len(self.__chunks)):
            chunk = self.__chunks[idx]
            if (absPos >= chunk.absPos and absPos <
                (chunk.absPos + len(chunk.data))):
                foundIdx = idx
                break

            if absPos < chunk.absPos:
                insertIdx = idx
                break

            ioDelta += len(chunk.data) - self.CHUNK_SIZE
            insertIdx = idx + 1

        if foundIdx == -1:
            newChunk = HexEditChunk()
            readAbsPos = absPos - ioDelta
            readPos = readAbsPos & self.READ_CHUNK_MASK
            self.__ioDevice.open(QIODevice.ReadOnly)
            self.__ioDevice.seek(readPos)
            newChunk.data = bytearray(self.__ioDevice.read(self.CHUNK_SIZE))
            self.__ioDevice.close()
            newChunk.absPos = absPos - (readAbsPos - readPos)
            newChunk.dataChanged = bytearray(len(newChunk.data))
            self.__chunks.insert(insertIdx, newChunk)
            foundIdx = insertIdx

        return foundIdx
예제 #13
0
    def requestStarted(self, job: QWebEngineUrlRequestJob):
        """Handle a request for all scheme.

        This method must be reimplemented by all custom URL scheme handlers.
        The request is asynchronous and does not need to be handled right away.

        Args:
            job: QWebEngineUrlRequestJob
        """
        url: QUrl = job.requestUrl()

        if url.scheme() in ["chrome-error", "chrome-extension"]:
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
            job.fail(QWebEngineUrlRequestJob.UrlInvalid)
            return

        # if not self._check_initiator(job):
        #     return

        if job.requestMethod() != b"GET":
            job.fail(QWebEngineUrlRequestJob.RequestDenied)
            return

        log.webview.debug("Got request for {}".format(url.toDisplayString()))
        if url.scheme() == "luminos":
            try:
                mimetype, data = Scheme.data_for_url(url)
            except Scheme.Error as e:
                errors = {
                    Scheme.NotFoundError: QWebEngineUrlRequestJob.UrlNotFound,
                    Scheme.UrlInvalidError: QWebEngineUrlRequestJob.UrlInvalid,
                    Scheme.RequestDeniedError:
                    QWebEngineUrlRequestJob.RequestDenied,
                    Scheme.SchemeOSError: QWebEngineUrlRequestJob.UrlNotFound,
                    Scheme.Error: QWebEngineUrlRequestJob.RequestFailed,
                }
                exctype = type(e)
                log.webview.error("{} while handling luminos://* URL".format(
                    exctype.__name__))
                job.fail(errors[exctype])
            except Scheme.Redirect as e:
                qtutils.ensure_valid(e.url)
                job.redirect(e.url)
            else:
                log.webview.debug("Returning {} data".format(mimetype))

                # We can't just use the QBuffer constructor taking a QByteArray,
                # because that somehow segfaults...
                # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
                buf = QBuffer(parent=self)
                buf.open(QIODevice.WriteOnly)
                buf.write(data)
                buf.seek(0)
                buf.close()
                job.reply(mimetype.encode("ascii"), buf)
        else:
            """
            If it's not our internal scheme then this probably user defined scheme
            """

            try:
                mimetype, data = Scheme.data_for_custom_scheme(url)
            except Scheme.NotFoundError:
                log.webview.debug("handling custom uri scheme for : {}".format(
                    url.toDisplayString()))
                """
                If it's throw error it can mean that user doesn't add their own handler,
                we will try to handle it ourselves here.
                """
                scheme = url.scheme()

                if self.schemes is None or not scheme in self.schemes.keys():
                    job.fail(QWebEngineUrlRequestJob.UrlNotFound)
                    return

                base_path = self.schemes[scheme]
                path = os.path.join(base_path, url.host(), url.path()[1:])

                if not os.path.exists(path):
                    job.fail(QWebEngineUrlRequestJob.UrlNotFound)
                    return

                try:
                    with open(path, 'rb') as file:

                        content_type = mimetypes.guess_type(path)
                        buff = QBuffer(parent=self)
                        buff.open(QIODevice.WriteOnly)
                        buff.write(file.read())
                        buff.seek(0)
                        buff.close()

                        job.reply(content_type[0].encode(), buff)
                except Exception as e:
                    raise e
            else:
                log.webview.debug("Returning {} data".format(mimetype))

                # We can't just use the QBuffer constructor taking a QByteArray,
                # because that somehow segfaults...
                # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
                buf = QBuffer(parent=self)
                buf.open(QIODevice.WriteOnly)
                buf.write(data)
                buf.seek(0)
                buf.close()
                job.reply(mimetype.encode("ascii"), buf)
예제 #14
0
class HexEditChunks(object):
    """
    Class implementing the storage backend for the hex editor.
    
    When HexEditWidget loads data, HexEditChunks access them using a QIODevice
    interface. When the app uses a QByteArray or Python bytearray interface,
    QBuffer is used to provide again a QIODevice like interface. No data will
    be changed, therefore HexEditChunks opens the QIODevice in
    QIODevice.ReadOnly mode. After every access HexEditChunks closes the
    QIODevice. That's why external applications can overwrite files while
    HexEditWidget shows them.

    When the the user starts to edit the data, HexEditChunks creates a local
    copy of a chunk of data (4 kilobytes) and notes all changes there. Parallel
    to that chunk, there is a second chunk, which keeps track of which bytes
    are changed and which are not.
    """
    BUFFER_SIZE = 0x10000
    CHUNK_SIZE = 0x1000
    READ_CHUNK_MASK = 0xfffffffffffff000
    
    def __init__(self, ioDevice=None):
        """
        Constructor
        
        @param ioDevice io device to get the data from
        @type QIODevice
        """
        self.__ioDevice = None
        self.__pos = 0
        self.__size = 0
        self.__chunks = []
        
        if ioDevice is None:
            buf = QBuffer()
            self.setIODevice(buf)
        else:
            self.setIODevice(ioDevice)
    
    def setIODevice(self, ioDevice):
        """
        Public method to set an io device to read the binary data from.
        
        @param ioDevice io device to get the data from
        @type QIODevice
        @return flag indicating successful operation
        @rtype bool
        """
        self.__ioDevice = ioDevice
        ok = self.__ioDevice.open(QIODevice.ReadOnly)
        if ok:
            # open successfully
            self.__size = self.__ioDevice.size()
            self.__ioDevice.close()
        else:
            # fallback is an empty buffer
            self.__ioDevice = QBuffer()
            self.__size = 0
        
        self.__chunks = []
        self.__pos = 0
        
        return ok
    
    def data(self, pos=0, maxSize=-1, highlighted=None):
        """
        Public method to get data out of the chunks.
        
        @param pos position to get bytes from
        @type int
        @param maxSize maximum amount of bytes to get
        @type int
        @param highlighted reference to a byte array storing highlighting info
        @type bytearray
        @return retrieved data
        @rtype bytearray
        """
        ioDelta = 0
        chunkIdx = 0
        
        chunk = HexEditChunk()
        buffer = bytearray()
        
        if highlighted is not None:
            del highlighted[:]
        
        if pos >= self.__size:
            return buffer
        
        if maxSize < 0:
            maxSize = self.__size
        elif (pos + maxSize) > self.__size:
            maxSize = self.__size - pos
        
        self.__ioDevice.open(QIODevice.ReadOnly)
        
        while maxSize > 0:
            chunk.absPos = sys.maxsize
            chunksLoopOngoing = True
            while chunkIdx < len(self.__chunks) and chunksLoopOngoing:
                # In this section, we track changes before our required data
                # and we take the edited data, if availible. ioDelta is a
                # difference counter to justify the read pointer to the
                # original data, if data in between was deleted or inserted.
                
                chunk = self.__chunks[chunkIdx]
                if chunk.absPos > pos:
                    chunksLoopOngoing = False
                else:
                    chunkIdx += 1
                    chunkOfs = pos - chunk.absPos
                    if maxSize > (len(chunk.data) - chunkOfs):
                        count = len(chunk.data) - chunkOfs
                        ioDelta += self.CHUNK_SIZE - len(chunk.data)
                    else:
                        count = maxSize
                    if count > 0:
                        buffer += chunk.data[chunkOfs:chunkOfs + count]
                        maxSize -= count
                        pos += count
                        if highlighted is not None:
                            highlighted += \
                                chunk.dataChanged[chunkOfs:chunkOfs + count]
            
            if maxSize > 0 and pos < chunk.absPos:
                # In this section, we read data from the original source. This
                # will only happen, when no copied data is available.
                if chunk.absPos - pos > maxSize:
                    byteCount = maxSize
                else:
                    byteCount = chunk.absPos - pos
                
                maxSize -= byteCount
                self.__ioDevice.seek(pos + ioDelta)
                readBuffer = bytearray(self.__ioDevice.read(byteCount))
                buffer += readBuffer
                if highlighted is not None:
                    highlighted += bytearray(len(readBuffer))
                pos += len(readBuffer)
        
        self.__ioDevice.close()
        return buffer
    
    def write(self, ioDevice, pos=0, count=-1):
        """
        Public method to write data to an io device.
        
        @param ioDevice io device to write the data to
        @type QIODevice
        @param pos position to write bytes from
        @type int
        @param count amount of bytes to write
        @type int
        @return flag indicating success
        @rtype bool
        """
        if count == -1:
            # write all data
            count = self.__size
        
        ok = ioDevice.open(QIODevice.WriteOnly)
        if ok:
            idx = pos
            while idx < count:
                data = self.data(idx, self.BUFFER_SIZE)
                ioDevice.write(QByteArray(data))
                
                # increment loop variable
                idx += self.BUFFER_SIZE
            
            ioDevice.close()
        
        return ok
    
    def setDataChanged(self, pos, dataChanged):
        """
        Public method to set highlighting info.
        
        @param pos position to set highlighting info for
        @type int
        @param dataChanged flag indicating changed data
        @type bool
        """
        if pos < 0 or pos >= self.__size:
            # position is out of range, do nothing
            return
        chunkIdx = self.__getChunkIndex(pos)
        posInChunk = pos - self.__chunks[chunkIdx].absPos
        self.__chunks[chunkIdx].dataChanged[posInChunk] = int(dataChanged)
    
    def dataChanged(self, pos):
        """
        Public method to test, if some data was changed.
        
        @param pos byte position to check
        @type int
        @return flag indicating the changed state
        @rtype bool
        """
        highlighted = bytearray()
        self.data(pos, 1, highlighted)
        return highlighted and bool(highlighted[0])
    
    def indexOf(self, byteArray, start):
        """
        Public method to search the first occurrence of some data.
        
        @param byteArray data to search for
        @type bytearray
        @param start position to start the search at
        @type int
        @return position the data was found at or -1 if nothing could be found
        @rtype int
        """
        ba = bytearray(byteArray)
        
        result = -1
        pos = start
        while pos < self.__size:
            buffer = self.data(pos, self.BUFFER_SIZE + len(ba) - 1)
            findPos = buffer.find(ba)
            if findPos >= 0:
                result = pos + findPos
                break
            
            # increment loop variable
            pos += self.BUFFER_SIZE
        
        return result
    
    def lastIndexOf(self, byteArray, start):
        """
        Public method to search the last occurrence of some data.
        
        @param byteArray data to search for
        @type bytearray
        @param start position to start the search at
        @type int
        @return position the data was found at or -1 if nothing could be found
        @rtype int
        """
        ba = bytearray(byteArray)
        
        result = -1
        pos = start
        while pos > 0 and result < 0:
            sPos = pos - self.BUFFER_SIZE - len(ba) + 1
            if sPos < 0:
                sPos = 0
            
            buffer = self.data(sPos, pos - sPos)
            findPos = buffer.rfind(ba)
            if findPos >= 0:
                result = sPos + findPos
                break
            
            # increment loop variable
            pos -= self.BUFFER_SIZE
        
        return result
    
    def insert(self, pos, data):
        """
        Public method to insert a byte.
        
        @param pos position to insert at
        @type int
        @param data byte to insert
        @type int (range 0 to 255)
        @return flag indicating success
        @rtype bool
        """
        if pos < 0 or pos > self.__size:
            # position is out of range, do nothing
            return False
        
        if pos == self.__size:
            chunkIdx = self.__getChunkIndex(pos - 1)
        else:
            chunkIdx = self.__getChunkIndex(pos)
        chunk = self.__chunks[chunkIdx]
        posInChunk = pos - chunk.absPos
        chunk.data.insert(posInChunk, data)
        chunk.dataChanged.insert(posInChunk, 1)
        for idx in range(chunkIdx + 1, len(self.__chunks)):
            self.__chunks[idx].absPos += 1
        self.__size += 1
        self.__pos = pos
        return True
    
    def overwrite(self, pos, data):
        """
        Public method to overwrite a byte.
        
        @param pos position to overwrite
        @type int
        @param data byte to overwrite with
        @type int (range 0 to 255)
        @return flag indicating success
        @rtype bool
        """
        if pos < 0 or pos >= self.__size:
            # position is out of range, do nothing
            return False
        
        chunkIdx = self.__getChunkIndex(pos)
        chunk = self.__chunks[chunkIdx]
        posInChunk = pos - chunk.absPos
        chunk.data[posInChunk] = data
        chunk.dataChanged[posInChunk] = 1
        self.__pos = pos
        return True
    
    def removeAt(self, pos):
        """
        Public method to remove a byte.
        
        @param pos position to remove
        @type int
        @return flag indicating success
        @rtype bool
        """
        if pos < 0 or pos >= self.__size:
            # position is out of range, do nothing
            return
        
        chunkIdx = self.__getChunkIndex(pos)
        chunk = self.__chunks[chunkIdx]
        posInChunk = pos - chunk.absPos
        chunk.data.pop(posInChunk)
        chunk.dataChanged.pop(posInChunk)
        for idx in range(chunkIdx + 1, len(self.__chunks)):
            self.__chunks[idx].absPos -= 1
        self.__size -= 1
        self.__pos = pos
        return True
    
    def __getitem__(self, pos):
        """
        Special method to get a byte at a position.
        
        Note: This realizes the [] get operator.
        
        @param pos position of byte to get
        @type int
        @return requested byte
        @rtype int (range 0 to 255)
        """
        if pos >= self.__size:
            return 0
##            raise IndexError
        
        return self.data(pos, 1)[0]
    
    def pos(self):
        """
        Public method to get the current position.
        
        @return current position
        @rtype int
        """
        return self.__pos
    
    def size(self):
        """
        Public method to get the current data size.
        
        @return current data size
        @rtype int
        """
        return self.__size
    
    def __getChunkIndex(self, absPos):
        """
        Private method to get the chunk index for a position.
        
        This method checks, if there is already a copied chunk available. If
        there is one, it returns its index. If there is no copied chunk
        available, original data will be copied into a new chunk.
        
        @param absPos absolute position of the data.
        @type int
        @return index of the chunk containing the position
        @rtype int
        """
        foundIdx = -1
        insertIdx = 0
        ioDelta = 0
        
        for idx in range(len(self.__chunks)):
            chunk = self.__chunks[idx]
            if absPos >= chunk.absPos and \
                    absPos < (chunk.absPos + len(chunk.data)):
                foundIdx = idx
                break
            
            if absPos < chunk.absPos:
                insertIdx = idx
                break
            
            ioDelta += len(chunk.data) - self.CHUNK_SIZE
            insertIdx = idx + 1
        
        if foundIdx == -1:
            newChunk = HexEditChunk()
            readAbsPos = absPos - ioDelta
            readPos = readAbsPos & self.READ_CHUNK_MASK
            self.__ioDevice.open(QIODevice.ReadOnly)
            self.__ioDevice.seek(readPos)
            newChunk.data = bytearray(self.__ioDevice.read(self.CHUNK_SIZE))
            self.__ioDevice.close()
            newChunk.absPos = absPos - (readAbsPos - readPos)
            newChunk.dataChanged = bytearray(len(newChunk.data))
            self.__chunks.insert(insertIdx, newChunk)
            foundIdx = insertIdx
        
        return foundIdx
예제 #15
0
class Window(QWidget):
    def __init__(self, parent=None):

        QWidget.__init__(self, parent)

        format = QAudioFormat()
        format.setChannelCount(1)
        format.setSampleRate(22050)
        format.setSampleSize(16)
        format.setCodec("audio/pcm")
        format.setByteOrder(QAudioFormat.LittleEndian)
        format.setSampleType(QAudioFormat.SignedInt)
        self.output = QAudioOutput(format, self)

        self.frequency = 440
        self.volume = 0
        self.buffer = QBuffer()
        self.data = QByteArray()

        self.deviceLineEdit = QLineEdit()
        self.deviceLineEdit.setReadOnly(True)
        self.deviceLineEdit.setText(
            QAudioDeviceInfo.defaultOutputDevice().deviceName())

        self.pitchSlider = QSlider(Qt.Horizontal)
        self.pitchSlider.setMaximum(100)
        self.volumeSlider = QSlider(Qt.Horizontal)
        self.volumeSlider.setMaximum(32767)
        self.volumeSlider.setPageStep(1024)

        self.playButton = QPushButton(self.tr("&Play"))

        self.pitchSlider.valueChanged.connect(self.changeFrequency)
        self.volumeSlider.valueChanged.connect(self.changeVolume)
        self.playButton.clicked.connect(self.play)

        formLayout = QFormLayout()
        formLayout.addRow(self.tr("Device:"), self.deviceLineEdit)
        formLayout.addRow(self.tr("P&itch:"), self.pitchSlider)
        formLayout.addRow(self.tr("&Volume:"), self.volumeSlider)

        buttonLayout = QVBoxLayout()
        buttonLayout.addWidget(self.playButton)
        buttonLayout.addStretch()

        horizontalLayout = QHBoxLayout(self)
        horizontalLayout.addLayout(formLayout)
        horizontalLayout.addLayout(buttonLayout)

        self.play()
        self.createData()

    def changeFrequency(self, value):

        self.frequency = 440 + (value * 2)
        self.createData()

    def play(self):

        if self.output.state() == QAudio.ActiveState:
            self.output.stop()

        if self.buffer.isOpen():
            self.buffer.close()

        if self.output.error() == QAudio.UnderrunError:
            self.output.reset()

        self.buffer.setData(self.data)
        self.buffer.open(QIODevice.ReadOnly)
        self.buffer.seek(0)

        self.output.start(self.buffer)

    def changeVolume(self, value):

        self.volume = value
        self.createData()

    def createData(self):

        self.data.clear()
        for i in range(2 * 22050):
            t = i / 22050.0
            value = int(self.volume * sin(2 * pi * self.frequency * t))
            self.data.append(struct.pack("<h", value))
예제 #16
0
class AudioWidget(QWidget):
    def __init__(self, parent=None):

        QWidget.__init__(self, parent)

        self.format = None
        self.output = None
        self.buffer = QBuffer()

        self.volumeSlider = QSlider(Qt.Horizontal)
        self.volumeSlider.setMaximum(10)
        self.volumeSlider.setPageStep(1)
        self.volumeSlider.setValue(5)

        self.playButton = QPushButton()
        self.playButton.setIcon(QIcon("icons/play.png"))

        self.stopButton = QPushButton()
        self.stopButton.setIcon(QIcon("icons/stop.png"))

        self.volumeSlider.valueChanged.connect(self.change_volume)
        self.playButton.clicked.connect(self.play_pause)
        self.stopButton.clicked.connect(self.stop)

        layout = QHBoxLayout(self)
        layout.addWidget(self.playButton)
        layout.addWidget(self.stopButton)
        layout.addWidget(self.volumeSlider)
        layout.addStretch()

    def stop(self):
        if self.output:
            if self.output.state() != QAudio.StoppedState:
                self.output.stop()

    def set_data(self, mono_sig, sr):
        # if not self.format:
        self.format = QAudioFormat()
        self.format.setChannelCount(1)
        self.format.setSampleRate(sr)
        #numpy is in bites, qt in bits
        self.format.setSampleSize(mono_sig.dtype.itemsize * 8)
        self.format.setCodec("audio/pcm")
        self.format.setByteOrder(QAudioFormat.LittleEndian)
        self.format.setSampleType(QAudioFormat.Float)
        self.output = QAudioOutput(self.format, self)
        self.output.stateChanged.connect(self.audio_state_changed)
        #change the content without stopping playback
        p = self.buffer.pos()
        if self.buffer.isOpen():
            self.buffer.close()

        self.data = mono_sig.tobytes()
        self.buffer.setData(self.data)
        self.buffer.open(QIODevice.ReadWrite)
        self.buffer.seek(p)

    def audio_state_changed(self, new_state):
        #adjust the button icon
        if new_state != QAudio.ActiveState:
            self.playButton.setIcon(QIcon("icons/play.png"))
        else:
            self.playButton.setIcon(QIcon("icons/pause.png"))

    def cursor(self, t):
        #seek towards the time t
        #todo: handle EOF case
        try:
            if self.format:
                t = max(0, t)
                b = self.format.bytesForDuration(t * 1000000)
                self.buffer.seek(b)
        except:
            print("cursor error")

    def play_pause(self):
        if self.output:
            #(un)pause the audio output, keeps the buffer intact
            if self.output.state() == QAudio.ActiveState:
                self.output.suspend()
            elif self.output.state() == QAudio.SuspendedState:
                self.output.resume()
            else:
                self.buffer.seek(0)
                self.output.start(self.buffer)

    def change_volume(self, value):
        if self.output:
            #need to wrap this because slider gives not float output
            self.output.setVolume(value / 10)
예제 #17
0
class MainWindow(QMainWindow):
    def __init__(self, wav_path):
        QMainWindow.__init__(self)
        self.resize(350, 250)
        self.setWindowTitle('MainWindow')

        self._setLayout()
        self.status_bar = self.statusBar()

        self.wav_path = wav_path
        self.params = utils.read_wav_info(wav_path)
        self.duration = self.params.nframes / self.params.framerate

        self.output = utils.get_audio_output(self.params)
        self.output.stateChanged.connect(self.state_checkpoint)
        self.output.setNotifyInterval(20)

        self.output.notify.connect(self.notified)

        self.loop_button.clicked.connect(self.switch_loop)
        self.play_button.clicked.connect(self.play_pause)
        self.random_button.clicked.connect(self.set_random_region)
        self.export_button.clicked.connect(self.export_region)

        self.command_edit.returnPressed.connect(self.command_entered)

        self.loop_enabled = False

        self.buffer = QBuffer()

        self.region = None
        self.set_region((0, REG_SECONDS * self.params.framerate))
        # self.set_random_region()

    def _setLayout(self):
        widget = QtWidgets.QWidget()

        grid = QtWidgets.QGridLayout(widget)
        self.progressBar = QtWidgets.QProgressBar(widget)
        self.progressBar.setRange(0, 100)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(True)

        self.loop_button = QtWidgets.QPushButton('Loop', widget)
        self.loop_button.setCheckable(True)
        self.play_button = QtWidgets.QPushButton('Play | Stop', widget)
        self.random_button = QtWidgets.QPushButton('Random', widget)
        self.command_edit = QtWidgets.QLineEdit('')
        self.export_button = QtWidgets.QPushButton('Export', widget)

        grid.addWidget(self.progressBar, 0, 0, 1, 3)
        grid.addWidget(self.loop_button, 1, 0)
        grid.addWidget(self.play_button, 1, 1)
        grid.addWidget(self.random_button, 1, 2)
        grid.addWidget(self.command_edit, 2, 1)
        grid.addWidget(self.export_button, 2, 2)

        widget.setLayout(grid)
        self.setCentralWidget(widget)

    def play(self):
        """
        Play from the beginning.
        """
        if self.buffer.isOpen():
            state = self.output.state()
            if state != QAudio.StoppedState:
                self.output.stop()
            if sys.platform == 'darwin':
                self.buffer.close()
                self.buffer.open(QIODevice.ReadOnly)
            else:
                # I found this way does not works on OS X
                self.buffer.seek(0)
        else:
            # Load from file
            self.buffer.open(QIODevice.ReadOnly)
        self.output.start(self.buffer)

    def play_pause(self):
        """
        Play or pause based on audio output state.
        """
        state = self.output.state()
        if state == QAudio.ActiveState:  # playing
            # pause playback
            self.output.suspend()
        elif state == QAudio.SuspendedState:  # paused
            # resume playback
            self.output.resume()
        elif state == QAudio.StoppedState or state == QAudio.IdleState:
            self.play()

    def stop(self):
        """
        Stop playback.
        """
        state = self.output.state()
        if state != QAudio.StoppedState:
            self.output.stop()
            if sys.platform == 'darwin':
                self.buffer.close()

    def switch_loop(self):
        self.loop_enabled = not self.loop_enabled

    def state_checkpoint(self):
        """
        React to AudioOutput state change.
        Loop if enabled.
        """
        # Loop implementation
        state = self.output.state()
        if state == QAudio.ActiveState:
            print(state, '== Active')
        elif state == QAudio.SuspendedState:
            print(state, '== Suspended')
        elif state == QAudio.IdleState:
            print(state, '== Idle')
            if self.loop_enabled:
                self.play()
            else:
                self.stop()
        elif state == QAudio.StoppedState:
            print(state, '== Stopped')

    def notified(self):
        start_time = self.region[0] / self.params.framerate
        playing_time = self.output.processedUSecs() / 1000000 + start_time
        self.progressBar.setValue(playing_time * 100 / self.duration)
        self.status_bar.showMessage(str(timedelta(seconds=playing_time))[:-3])

    def set_region(self, region):
        """
        Put the playback start position to `position`.
        """
        # avoid segfault if changing region during playback
        self.stop()

        position, end = region
        position = max(0, min(position, end))  # don't start before 0
        end = min(self.params.nframes, end)  # don't set end after days!
        self.region = position, end
        print('set_region -> {:,}-{:,}'.format(*self.region))
        print('region times: {}-{} (duration={})'.format(*self.region_timedeltas()))
        frame_to_read = end - position

        wav = wave.open(self.wav_path)
        wav.setpos(position)
        # we need to reinit buffer since the region could be shorter than before
        self.buffer = QBuffer()
        self.buffer.writeData(wav.readframes(frame_to_read))
        wav.close()

        start_time = position / self.params.framerate
        self.progressBar.setValue(start_time * 100 / self.duration)
        self.status_bar.showMessage(str(timedelta(seconds=start_time))[:-3])

    @property
    def reg_nframes(self):
        return self.region[1] - self.region[0]

    def set_random_region(self):
        """
        Choose a random position and set playback start from there.
        """
        try:
            position = random.randrange(self.params.nframes - self.reg_nframes)
        except ValueError:
            print('Cannot move position randomly. Please shorten the region.')
            position = 0

        end = position + self.reg_nframes
        print('Random region: {:.2f}-{:.2f}'.format(
            position / self.params.framerate, end / self.params.framerate)
        )
        self.set_region((position, end))

    def region_timedeltas(self):
        """Return start, end and duration timedeltas"""
        start, end = self.region
        start_timedelta = timedelta(seconds=start / self.params.framerate)
        end_timedelta = timedelta(seconds=end / self.params.framerate)
        return start_timedelta, end_timedelta, (end_timedelta - start_timedelta)

    def command_entered(self):
        """
        Change region boundaries with Blender-like syntax.

        Examples:
        "l-0.5" ==> move start position 0.5 s before
        "r1"    ==> move stop position 1 seconds after
        """
        command = self.command_edit.text()
        try:
            lr, delta = utils.parse_command(command)
        except (IndexError, ValueError) as err:
            print(err)
            return

        start, end = self.region
        if lr == 'l':
            start = int(start + delta * self.params.framerate)
            print('New start: {}'.format(timedelta(seconds=(start / self.params.framerate))))
        elif lr == 'r':
            end = int(end + delta * self.params.framerate)
            print('New end: {}'.format(timedelta(seconds=(end / self.params.framerate))))

        self.set_region((start, end))
        self.command_edit.setText('')

        # feature: restart immediately after command is entered
        self.play()

    def export_region(self):
        """
        Export the current region.
        """
        start, stop = self.region
        wav_filepath = self.wav_path[:-4] + '[{}-{}].wav'.format(start, stop)
        with wave.open(wav_filepath, 'wb') as wave_write:
            wave_write.setparams(self.params)
            wave_write.writeframes(self.buffer.data())
        print(wav_filepath, 'created')
예제 #18
0
파일: Chunks.py 프로젝트: Andres6936/Hex
class Chunks(QObject):
    def __init__(self, parent: QObject = None, device: QIODevice = None):
        super().__init__(parent)
        self.device = QBuffer(self) if device is None else device
        self.chunks: [Chunk] = []
        self.position = 0
        self.size = 0

        self.setIODevice(self.device)

    def setIODevice(self, device: QIODevice) -> bool:
        self.device = device
        status = self.device.open(QIODevice.ReadOnly)
        if status:
            self.size = self.device.size()
            self.device.close()
        else:
            # Fallback is an empty buffer
            self.size = 0
            self.device = QBuffer(self)
        self.chunks.clear()
        self.position = 0
        return status

    def data(self,
             position: int,
             maxSize: int = -1,
             highlighted: QByteArray = None) -> QByteArray:
        delta = 0
        chunkIdx = 0
        chunk = Chunk()
        buffer = QByteArray()
        if highlighted:
            highlighted.clear()
        if position >= self.size:
            return buffer
        if maxSize < 0:
            maxSize = self.size
        elif (position + maxSize) > self.size:
            maxSize = self.size - position

        self.device.open(QIODevice.ReadOnly)
        while maxSize > 0:
            chunk.absPos = sys.maxsize
            chunksLoopOngoing = True
            while chunkIdx < len(self.chunks) and chunksLoopOngoing:
                # In this section, we track changes before our required data and
                # we take the editdet data, if availible. ioDelta is a difference
                # counter to justify the read pointer to the original data, if
                # data in between was deleted or inserted.
                chunk = self.chunks[chunkIdx]
                if chunk.absPos > position:
                    chunksLoopOngoing = False
                else:
                    count = 0
                    chunkIdx += 1
                    chunkOfs = position - chunk.absPos
                    if maxSize > chunk.data.size() - chunkOfs:
                        count = chunk.data.size() - chunkOfs
                        delta += CHUNK_SIZE - chunk.data.size()
                    else:
                        count = maxSize

                    if count > 0:
                        buffer += chunk.data.mid(chunkOfs, count)
                        maxSize -= count
                        position += count
                        if highlighted:
                            highlighted += chunk.dataChanged.mid(
                                chunkOfs, count)
            if maxSize > 0 and position < chunk.absPos:
                byteCount = 0
                if (chunk.absPos - position) > maxSize:
                    byteCount = maxSize
                else:
                    byteCount = chunk.absPos - position
                maxSize -= byteCount
                self.device.seek(position + delta)
                readBuffer = self.device.read(byteCount)
                buffer += readBuffer
                if highlighted:
                    highlighted += QByteArray(readBuffer.size(), NORMAL)
                position += len(readBuffer)
        self.device.close()
        return buffer

    def write(self,
              device: QIODevice,
              position: int = 0,
              count: int = -1) -> bool:
        if count == -1:
            count = self.size
        status = device.open(QIODevice.WriteOnly)
        if status:
            for idx in range(position, count, BUFFER_SIZE):
                array = self.data(idx, BUFFER_SIZE)
                device.write(array)
            device.close()
        return status

    def setDataChanged(self, position: int, dataChanged: bool) -> None:
        pass

    def dataChanged(self, position: int) -> bool:
        pass

    def indexOf(self, array: QByteArray, _from: int) -> int:
        pass

    def lastIndexOf(self, array: QByteArray, _from: int) -> int:
        pass

    def insert(self, position: int, character: str) -> bool:
        pass

    def overwrite(self, position: int, character: str) -> bool:
        pass

    def removeAt(self, position: int) -> bool:
        pass

    def getPosition(self) -> int:
        pass

    def getSize(self) -> int:
        pass

    def __getitem__(self, item):
        pass