diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index e9b082794b9..b4aba49b04d 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -68,17 +68,6 @@ Database Access sage/databases/sql_db -Media -~~~~~ - -.. toctree:: - :maxdepth: 1 - - sage/structure/graphics_file - sage/media/wav -.. underscore-methods only -.. sage/media/channels - Warnings ~~~~~~~~ diff --git a/src/sage/all.py b/src/sage/all.py index 7d9e33c7c80..b54610c819d 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -132,8 +132,6 @@ from sage.games.all import * -lazy_import('sage.media.wav', 'Wave', as_='wave', deprecation=12673) - from sage.logic.all import * from sage.numerical.all import * diff --git a/src/sage/media/__init__.py b/src/sage/media/__init__.py deleted file mode 100644 index 8b3785e6874..00000000000 --- a/src/sage/media/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from sage.misc.superseded import deprecation -deprecation(12673, "the package sage.media is deprecated") diff --git a/src/sage/media/all.py b/src/sage/media/all.py deleted file mode 100644 index 986d735f2c2..00000000000 --- a/src/sage/media/all.py +++ /dev/null @@ -1 +0,0 @@ -from .wav import Wave as wave diff --git a/src/sage/media/channels.pyx b/src/sage/media/channels.pyx deleted file mode 100644 index 448dc350b42..00000000000 --- a/src/sage/media/channels.pyx +++ /dev/null @@ -1,34 +0,0 @@ -"_separate_channels" - - -def _separate_channels(_data, _width, _nchannels): - """ - Separate the channels. This is an internal helper method for - precomputing some data while initializing the wav object. - """ - cdef int nchannels = _nchannels - cdef int width = _width - cdef char* data = _data - cdef int n - cdef short int x - - cdef int ell = len(_data) // width - - channel_data = [[] for i in range(nchannels)] - if width == 1: - # handle the one byte case - - for n in range(ell): - channel_data[n % nchannels].append(ord(data[n]) - 127) - - elif width == 2: - # a = 32768 - for n in range(ell): - # compute the value as an integer - x = (data[2*n]) + 256 * (data[2 * n + 1]) - # x -= 65536*(x > a) - channel_data[n % nchannels].append(x) - else: - raise NotImplementedError("greater than 16-bit wavs not supported") - - return channel_data diff --git a/src/sage/media/wav.py b/src/sage/media/wav.py deleted file mode 100644 index 9e9664595b3..00000000000 --- a/src/sage/media/wav.py +++ /dev/null @@ -1,402 +0,0 @@ -r""" -Work with WAV files - -A WAV file is a header specifying format information, followed by a -sequence of bytes, representing the state of some audio signal over a -length of time. - -A WAV file may have any number of channels. Typically, they have 1 -(mono) or 2 (for stereo). The data of a WAV file is given as a -sequence of frames. A frame consists of samples. There is one sample -per channel, per frame. Every wav file has a sample width, or, the -number of bytes per sample. Typically this is either 1 or 2 bytes. - -The wav module supplies more convenient access to this data. In -particular, see the docstring for ``Wave.channel_data()``. - -The header contains information necessary for playing the WAV file, -including the number of frames per second, the number of bytes per -sample, and the number of channels in the file. - -AUTHORS: - -- Bobby Moretti and Gonzalo Tornaria (2007-07-01): First version -- William Stein (2007-07-03): add more -- Bobby Moretti (2007-07-03): add doctests - -This module (and all of ``sage.media``) is deprecated. - -EXAMPLES:: - - sage: import sage.media - doctest:warning... - DeprecationWarning: the package sage.media is deprecated - See https://github.com/sagemath/sage/issues/12673 for details. -""" - -import math -import os -import wave - -from sage.misc.lazy_import import lazy_import -lazy_import("sage.plot.plot", "list_plot") -from sage.structure.sage_object import SageObject -from sage.arith.srange import srange -from sage.misc.html import html -from sage.rings.real_double import RDF - - -class Wave(SageObject): - """ - A class wrapping a wave audio file. - - INPUT: - - You must call Wave() with either data = filename, where - filename is the name of a wave file, or with each of the - following options: - - - channels -- the number of channels in the wave file (1 for mono, 2 for - stereo, etc... - - width -- the number of bytes per sample - - framerate -- the number of frames per second - - nframes -- the number of frames in the data stream - - bytes -- a string object containing the bytes of the data stream - - Slicing: - - Slicing a Wave object returns a new wave object that has been - trimmed to the bytes that you have given it. - - Indexing: - - Getting the `n`-th item in a Wave object will give you the value - of the `n`-th frame. - """ - def __init__(self, data=None, **kwds): - if data is not None: - self._filename = data - self._name = os.path.split(data)[1] - wv = wave.open(data, "rb") - self._nchannels = wv.getnchannels() - self._width = wv.getsampwidth() - self._framerate = wv.getframerate() - self._nframes = wv.getnframes() - self._bytes = wv.readframes(self._nframes) - from .channels import _separate_channels - self._channel_data = _separate_channels(self._bytes, - self._width, - self._nchannels) - wv.close() - elif kwds: - try: - self._name = kwds['name'] - self._nchannels = kwds['nchannels'] - self._width = kwds['width'] - self._framerate = kwds['framerate'] - self._nframes = kwds['nframes'] - self._bytes = kwds['bytes'] - self._channel_data = kwds['channel_data'] - except KeyError as msg: - raise KeyError(msg + " invalid input to Wave initializer") - else: - raise ValueError("Must give a filename") - - def save(self, filename='sage.wav'): - r""" - Save this wave file to disk, either as a Sage sobj or as a .wav file. - - INPUT: - - filename -- the path of the file to save. If filename ends - with 'wav', then save as a wave file, - otherwise, save a Sage object. - - If no input is given, save the file as 'sage.wav'. - """ - if not filename.endswith('.wav'): - SageObject.save(self, filename) - return - wv = wave.open(filename, 'wb') - wv.setnchannels(self._nchannels) - wv.setsampwidth(self._width) - wv.setframerate(self._framerate) - wv.setnframes(self._nframes) - wv.writeframes(self._bytes) - wv.close() - - def listen(self): - """ - Listen to (or download) this wave file. - - Creates a link to this wave file in the notebook. - """ - i = 0 - fname = 'sage%s.wav'%i - while os.path.exists(fname): - i += 1 - fname = 'sage%s.wav'%i - - self.save(fname) - return html('Click to listen to %s'%(fname, self._name)) - - def channel_data(self, n): - """ - Get the data from a given channel. - - INPUT: - - n -- the channel number to get - - OUTPUT: - - A list of signed ints, each containing the value of a frame. - """ - return self._channel_data[n] - - def getnchannels(self): - """ - Return the number of channels in this wave object. - - OUTPUT: - - The number of channels in this wave file. - """ - return self._nchannels - - def getsampwidth(self): - """ - Return the number of bytes per sample in this wave object. - - OUTPUT: - - The number of bytes in each sample. - """ - return self._width - - def getframerate(self): - """ - Return the number of frames per second in this wave object. - - OUTPUT: - - The frame rate of this sound file. - """ - return self._framerate - - def getnframes(self): - """ - Return the total number of frames in this wave object. - - OUTPUT: - - The number of frames in this WAV. - """ - return self._nframes - - def readframes(self, n): - """ - Read out the raw data for the first `n` frames of this wave object. - - INPUT: - - n -- the number of frames to return - - OUTPUT: - - A list of bytes (in string form) representing the raw wav data. - """ - return self._bytes[:self._nframes * self._width] - - def getlength(self): - """ - Return the length of this file (in seconds). - - OUTPUT: - - The running time of the entire WAV object. - """ - return float(self._nframes) / (self._nchannels * float(self._framerate)) - - def _repr_(self): - nc = self.getnchannels() - return "Wave file %s with %s channel%s of length %s seconds%s" % \ - (self._name, nc, "" if nc == 1 else "s", self.getlength(), "" if nc == 1 else " each") - - def _normalize_npoints(self, npoints): - """ - Used internally while plotting to normalize the number of - """ - return npoints if npoints else self._nframes - - def domain(self, npoints=None): - """ - Used internally for plotting. Get the x-values for the various points to plot. - """ - npoints = self._normalize_npoints(npoints) - # figure out on what intervals to sample the data - seconds = float(self._nframes) / float(self._width) - frame_duration = seconds / (float(npoints) * float(self._framerate)) - - domain = [n * frame_duration for n in range(npoints)] - return domain - - def values(self, npoints=None, channel=0): - """ - Used internally for plotting. Get the y-values for the various points to plot. - """ - npoints = self._normalize_npoints(npoints) - - # now, how many of the frames do we sample? - frame_skip = int(self._nframes / npoints) - # the values of the function at each point in the domain - cd = self.channel_data(channel) - - # now scale the values - scale = float(1 << (8*self._width -1)) - values = [cd[frame_skip*i]/scale for i in range(npoints)] - return values - - def set_values(self, values, channel=0): - """ - Used internally for plotting. Get the y-values for the various points to plot. - """ - c = self.channel_data(channel) - npoints = len(c) - if len(values) != npoints: - raise ValueError("values (of length %s) must have length %s"%(len(values), npoints)) - - # unscale the values - scale = float(1 << (8*self._width -1)) - values = [float(abs(s)) * scale for s in values] - - # the values of the function at each point in the domain - c = self.channel_data(channel) - for i in range(npoints): - c[i] = values[i] - - def vector(self, npoints=None, channel=0): - npoints = self._normalize_npoints(npoints) - - V = RDF**npoints - return V(self.values(npoints=npoints, channel=channel)) - - def plot(self, npoints=None, channel=0, plotjoined=True, **kwds): - """ - Plots the audio data. - - INPUT: - - - npoints -- number of sample points to take; if not given, draws all - known points. - - channel -- 0 or 1 (if stereo). default: 0 - - plotjoined -- whether to just draw dots or draw lines between sample points - - OUTPUT: - - a plot object that can be shown. - """ - domain = self.domain(npoints=npoints) - values = self.values(npoints=npoints, channel=channel) - points = zip(domain, values) - - L = list_plot(points, plotjoined=plotjoined, **kwds) - L.xmin(0) - L.xmax(domain[-1]) - return L - - def plot_fft(self, npoints=None, channel=0, half=True, **kwds): - v = self.vector(npoints=npoints) - w = v.fft() - if half: - w = w[:len(w)//2] - z = [abs(x) for x in w] - if half: - r = math.pi - else: - r = 2*math.pi - data = zip(srange(0, r, r/len(z)), z) - L = list_plot(data, plotjoined=True, **kwds) - L.xmin(0) - L.xmax(r) - return L - - def plot_raw(self, npoints=None, channel=0, plotjoined=True, **kwds): - npoints = self._normalize_npoints(npoints) - seconds = float(self._nframes) / float(self._width) - sample_step = seconds / float(npoints) - domain = [float(n*sample_step) / float(self._framerate) for n in range(npoints)] - frame_skip = self._nframes / npoints - values = [self.channel_data(channel)[frame_skip*i] for i in range(npoints)] - points = zip(domain, values) - - return list_plot(points, plotjoined=plotjoined, **kwds) - - def __getitem__(self, i): - """ - Return the `i`-th frame of data in the wave, in the form of a string, - if `i` is an integer. - - Return a slice of self if `i` is a slice. - """ - if isinstance(i, slice): - start, stop, step = i.indices(self._nframes) - return self._copy(start, stop) - else: - n = i*self._width - return self._bytes[n:n+self._width] - - def slice_seconds(self, start, stop): - """ - Slice the wave from start to stop. - - INPUT: - - start -- the time index from which to begin the slice (in seconds) - stop -- the time index from which to end the slice (in seconds) - - OUTPUT: - - A Wave object whose data is this object's data, - sliced between the given time indices - """ - start = int(start*self.getframerate()) - stop = int(stop*self.getframerate()) - return self[start:stop] - - # start and stop are frame numbers - def _copy(self, start, stop): - start = start * self._width - stop = stop * self._width - channels_sliced = [self._channel_data[i][start:stop] for i in range(self._nchannels)] - print(stop - start) - - return Wave(nchannels=self._nchannels, - width=self._width, - framerate=self._framerate, - bytes=self._bytes[start:stop], - nframes=stop - start, - channel_data=channels_sliced, - name=self._name) - - def __copy__(self): - return self._copy(0, self._nframes) - - def convolve(self, right, channel=0): - """ - NOT DONE! - - Convolution of self and other, i.e., add their fft's, then - inverse fft back. - """ - if not isinstance(right, Wave): - raise TypeError("right must be a wave") - npoints = self._nframes - v = self.vector(npoints, channel=channel).fft() - w = right.vector(npoints, channel=channel).fft() - k = v + w - i = k.inv_fft() - conv = self.__copy__() - conv.set_values(list(i)) - conv._name = "convolution of %s and %s" % (self._name, right._name) - return conv diff --git a/src/sage/structure/graphics_file.py b/src/sage/structure/graphics_file.py deleted file mode 100644 index 929b99c1ccb..00000000000 --- a/src/sage/structure/graphics_file.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Wrapper for Graphics Files - -This module is deprecated. -""" - -import os - -from sage.misc.temporary_file import tmp_filename -from sage.misc.superseded import deprecation -from sage.structure.sage_object import SageObject -import sage.doctest - - -deprecation(32988, 'the module sage.structure.graphics_file is deprecated') - - -class Mime(): - TEXT = 'text/plain' - HTML = 'text/html' - LATEX = 'text/latex' - JSON = 'application/json' - JAVASCRIPT = 'application/javascript' - PDF = 'application/pdf' - PNG = 'image/png' - JPG = 'image/jpeg' - SVG = 'image/svg+xml' - - JMOL = 'application/jmol' - - @classmethod - def validate(cls, value): - """ - Check that input is known mime type - - INPUT: - - - ``value`` -- string. - - OUTPUT: - - Unicode string of that mime type. A ``ValueError`` is raised - if input is incorrect / unknown. - - EXAMPLES:: - - sage: from sage.structure.graphics_file import Mime - doctest:warning... - DeprecationWarning: the module sage.structure.graphics_file is deprecated - See https://github.com/sagemath/sage/issues/32988 for details. - sage: Mime.validate('image/png') - 'image/png' - sage: Mime.validate('foo/bar') - Traceback (most recent call last): - ... - ValueError: unknown mime type - """ - value = str(value).lower() - for k, v in cls.__dict__.items(): - if isinstance(v, str) and v == value: - return v - raise ValueError('unknown mime type') - - @classmethod - def extension(cls, mime_type): - """ - Return file extension. - - INPUT: - - - ``mime_type`` -- mime type as string. - - OUTPUT: - - String containing the usual file extension for that type of - file. Excludes ``os.extsep``. - - EXAMPLES:: - - sage: from sage.structure.graphics_file import Mime - sage: Mime.extension('image/png') - 'png' - """ - try: - return preferred_filename_ext[mime_type] - except KeyError: - raise ValueError('no known extension for mime type') - - -preferred_filename_ext = { - Mime.TEXT: 'txt', - Mime.HTML: 'html', - Mime.LATEX: 'tex', - Mime.JSON: 'json', - Mime.JAVASCRIPT: 'js', - Mime.PDF: 'pdf', - Mime.PNG: 'png', - Mime.JPG: 'jpg', - Mime.SVG: 'svg', - Mime.JMOL: 'spt.zip', -} - - -mimetype_for_ext = dict( - (value, key) for (key, value) in preferred_filename_ext.items() -) - - -class GraphicsFile(SageObject): - - def __init__(self, filename, mime_type=None): - """ - Wrapper around a graphics file. - """ - self._filename = filename - if mime_type is None: - mime_type = self._guess_mime_type(filename) - self._mime = Mime.validate(mime_type) - - def _guess_mime_type(self, filename): - """ - Guess mime type from file extension - """ - ext = os.path.splitext(filename)[1] - ext = ext.lstrip(os.path.extsep) - try: - return mimetype_for_ext[ext] - except KeyError: - raise ValueError('unknown file extension, please specify mime type') - - def _repr_(self): - """ - Return a string representation. - """ - return 'Graphics file {0}'.format(self.mime()) - - def filename(self): - return self._filename - - def save_as(self, filename): - """ - Make the file available under a new filename. - - INPUT: - - - ``filename`` -- string. The new filename. - - The newly-created ``filename`` will be a hardlink if - possible. If not, an independent copy is created. - """ - try: - os.link(self.filename(), filename) - except OSError: - import shutil - shutil.copy2(self.filename(), filename) - - def mime(self): - return self._mime - - def data(self): - """ - Return a byte string containing the image file. - """ - with open(self._filename, 'rb') as f: - return f.read() - - def launch_viewer(self): - """ - Launch external viewer for the graphics file. - - .. note:: - - Does not actually launch a new process when doctesting. - - EXAMPLES:: - - sage: from sage.structure.graphics_file import GraphicsFile - sage: g = GraphicsFile('/tmp/test.png', 'image/png') - sage: g.launch_viewer() - """ - if sage.doctest.DOCTEST_MODE: - return - if self.mime() == Mime.JMOL: - return self._launch_jmol() - from sage.misc.viewer import viewer - command = viewer(preferred_filename_ext[self.mime()]) - os.system('{0} {1} 2>/dev/null 1>/dev/null &' - .format(command, self.filename())) - # TODO: keep track of opened processes... - - def _launch_jmol(self): - launch_script = tmp_filename(ext='.spt') - with open(launch_script, 'w') as f: - f.write('set defaultdirectory "{0}"\n'.format(self.filename())) - f.write('script SCRIPT\n') - os.system('jmol {0} 2>/dev/null 1>/dev/null &' - .format(launch_script)) - - -def graphics_from_save(save_function, preferred_mime_types, - allowed_mime_types=None, figsize=None, dpi=None): - """ - Helper function to construct a graphics file. - - INPUT: - - - ``save_function`` -- callable that can save graphics to a file - and accepts options like - :meth:`sage.plot.graphics.Graphics.save`. - - - ``preferred_mime_types`` -- list of mime types. The graphics - output mime types in order of preference (i.e. best quality to - worst). - - - ``allowed_mime_types`` -- set of mime types (as strings). The - graphics types that we can display. Output, if any, will be one - of those. - - - ``figsize`` -- pair of integers (optional). The desired graphics - size in pixels. Suggested, but need not be respected by the - output. - - - ``dpi`` -- integer (optional). The desired resolution in dots - per inch. Suggested, but need not be respected by the output. - - OUTPUT: - - Return an instance of - :class:`sage.structure.graphics_file.GraphicsFile` encapsulating a - suitable image file. Image is one of the - ``preferred_mime_types``. If ``allowed_mime_types`` is specified, - the resulting file format matches one of these. - - Alternatively, this function can return ``None`` to indicate that - textual representation is preferable and/or no graphics with the - desired mime type can be generated. - """ - # Figure out best mime type - mime = None - if allowed_mime_types is None: - mime = Mime.PNG - else: - # order of preference - for m in preferred_mime_types: - if m in allowed_mime_types: - mime = m - break - if mime is None: - return None # don't know how to generate suitable graphics - # Generate suitable temp file - filename = tmp_filename(ext=os.path.extsep + Mime.extension(mime)) - # Call the save_function with the right arguments - kwds = {} - if figsize is not None: - kwds['figsize'] = figsize - if dpi is not None: - kwds['dpi'] = dpi - save_function(filename, **kwds) - return GraphicsFile(filename, mime)