midd-media 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: midd-media
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Christopher Andrews
|
|
6
|
+
Author-email: candrews@middlebury.edu
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: pillow (>=11.1.0,<12.0.0)
|
|
15
|
+
Requires-Dist: pygame (>=2.6.1,<3.0.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# midd-media
|
|
19
|
+
Libraries and examples for CSCI 0145
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## middsound
|
|
23
|
+
|
|
24
|
+
A library for interacting with sound. This provides a thin wrapper around the wave library. This also requires pygame for sound playback.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
##middimage
|
|
28
|
+
|
|
29
|
+
Another thin wrapper to simplify the interface. This wraps pillow while providing an interface very close to that of middsound.
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# midd-media
|
|
2
|
+
Libraries and examples for CSCI 0145
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## middsound
|
|
6
|
+
|
|
7
|
+
A library for interacting with sound. This provides a thin wrapper around the wave library. This also requires pygame for sound playback.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
##middimage
|
|
11
|
+
|
|
12
|
+
Another thin wrapper to simplify the interface. This wraps pillow while providing an interface very close to that of middsound.
|
|
13
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "midd-media"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Christopher Andrews",email = "candrews@middlebury.edu"},
|
|
7
|
+
{name = "Andrea Vaccari",email = "vaccari@middlebury.edu"}
|
|
8
|
+
]
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pygame (>=2.6.1,<3.0.0)",
|
|
13
|
+
"pillow (>=11.1.0,<12.0.0)"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.poetry]
|
|
17
|
+
packages = [{include = "midd_media", from = "src"}]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
22
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This module provides a thin wrapper around pillow allowing us to easily create and manipulate images.
|
|
4
|
+
|
|
5
|
+
@author: C. Andrews, A. Vaccari
|
|
6
|
+
|
|
7
|
+
2022-04-20 Modified __repr__ to fit an entire line if 8-bit mono
|
|
8
|
+
2025-01-06 Removed the dependency on `numpy` and added a `Row` class to allow for 2D array notation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from PIL import Image
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MiddImage:
|
|
16
|
+
"""
|
|
17
|
+
This class provides a thin wrapper around the PIL Image class. It boils access down to some basic functionality and also allows for direct access to the pixels.
|
|
18
|
+
|
|
19
|
+
Access to the pixels can be done using 2D array notation (e.g., img[x][y]) or using a tuple (e.g., img[x,y]). The former will feel more natural to new Python programmers, but is 50% slower.
|
|
20
|
+
"""
|
|
21
|
+
class Row:
|
|
22
|
+
"""
|
|
23
|
+
Hidden helper class that allows us to access a row of pixels using 2D array notation.
|
|
24
|
+
"""
|
|
25
|
+
def __init__(self, image, row):
|
|
26
|
+
self.image = image
|
|
27
|
+
self.row = row
|
|
28
|
+
|
|
29
|
+
def __getitem__(self, index):
|
|
30
|
+
return self.image[self.row, index]
|
|
31
|
+
|
|
32
|
+
def __setitem__(self, index, value):
|
|
33
|
+
self.image[self.row, index] = value
|
|
34
|
+
|
|
35
|
+
def __init__(self, data=None, width=None, height=None, mode='RGB' ):
|
|
36
|
+
if data is None:
|
|
37
|
+
self._data = Image.new(mode, (width, height))
|
|
38
|
+
else:
|
|
39
|
+
self._data = data
|
|
40
|
+
self._pixels = self._data.load()
|
|
41
|
+
|
|
42
|
+
def __getitem__(self, index):
|
|
43
|
+
"""
|
|
44
|
+
Get the pixel at the specified 'index'.
|
|
45
|
+
The 'index' should be a tuple.
|
|
46
|
+
"""
|
|
47
|
+
if type(index) == int:
|
|
48
|
+
return MiddImage.Row(self, index)
|
|
49
|
+
else:
|
|
50
|
+
return self._pixels[index]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def __setitem__(self, index, value):
|
|
54
|
+
"""
|
|
55
|
+
Set the pixel at location 'index'.
|
|
56
|
+
The 'value' should be a three channel tuple.
|
|
57
|
+
"""
|
|
58
|
+
self._pixels[index] = value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def __getattr__(self, attr):
|
|
62
|
+
"""
|
|
63
|
+
Passes through attributes to the underlying image object.
|
|
64
|
+
|
|
65
|
+
Most important attributes are `width`,`height`, and `mode`.
|
|
66
|
+
"""
|
|
67
|
+
return getattr(self._data, attr)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def __repr__(self):
|
|
71
|
+
"""
|
|
72
|
+
Show the pixels of the image.
|
|
73
|
+
"""
|
|
74
|
+
return repr(list(self._data.getdata()))
|
|
75
|
+
|
|
76
|
+
def save(self, filename):
|
|
77
|
+
"""
|
|
78
|
+
Save the image as 'filename' into the current working directory.
|
|
79
|
+
"""
|
|
80
|
+
self._data.save(filename)
|
|
81
|
+
|
|
82
|
+
def show(self):
|
|
83
|
+
"""
|
|
84
|
+
Show the image.
|
|
85
|
+
"""
|
|
86
|
+
self._data.show()
|
|
87
|
+
|
|
88
|
+
def copy(self):
|
|
89
|
+
"""
|
|
90
|
+
Returns a new MiddImage with identical contents.
|
|
91
|
+
"""
|
|
92
|
+
new_image = MiddImage(self._data.copy())
|
|
93
|
+
return new_image
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def new(width, height, mode='RGB'):
|
|
97
|
+
"""
|
|
98
|
+
Create a new image with the specified width and height.
|
|
99
|
+
"""
|
|
100
|
+
return MiddImage(width=width, height=height, mode=mode)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def open(filename):
|
|
104
|
+
"""
|
|
105
|
+
Open an image file located in 'filename' (which is assumed to be in the working directory
|
|
106
|
+
or an absolute path.
|
|
107
|
+
"""
|
|
108
|
+
image_data = Image.open(filename)
|
|
109
|
+
new_image = MiddImage(data=image_data)
|
|
110
|
+
return new_image
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This module provides a thin wrapper around the wave library allowing us to
|
|
4
|
+
easily create and manipulate wav files.
|
|
5
|
+
|
|
6
|
+
@author: C. Andrews, A. Vaccari
|
|
7
|
+
|
|
8
|
+
Update: 2024-10-18 - Included the allowedchanges=0 option in pygame.mixer.init()
|
|
9
|
+
to solve an issue with double speed in Windows.
|
|
10
|
+
Update: 2024-03-26 - Updated to use pygame (simpleaudio is no longer maintained)
|
|
11
|
+
Update: 2022-04-20 - Added ValueError exception when trying to play an empty sound
|
|
12
|
+
Update: 2019-11-03 - Added support for sharps: capital letters will produce sharps
|
|
13
|
+
Update: 2019-04-02 - Added support for len(), removed extraneous functions
|
|
14
|
+
Update: 2016-08-23 - Changed play to use system tools
|
|
15
|
+
Update: 2016-08-22 - Converted to use __getitem__ and __setitem__
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import wave
|
|
20
|
+
import array
|
|
21
|
+
import pygame
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# These two constants are the maximum and minimum values allowed for our samples
|
|
25
|
+
MAXVALUE = 32677
|
|
26
|
+
MINVALUE = -32768
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Sound:
|
|
30
|
+
"""
|
|
31
|
+
This class is a container that holds one 'sound'. It provides an interface
|
|
32
|
+
mirroring the wave library that hides some of the details such as setting the
|
|
33
|
+
framerate and the sample width, as well as the interactions with the array
|
|
34
|
+
library which allows us to build the sound before writing it out to a file on
|
|
35
|
+
disk.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, framerate=44100):
|
|
39
|
+
"""
|
|
40
|
+
Initialize the sound with a data store for our samples and a default
|
|
41
|
+
sample rate of 44kHz
|
|
42
|
+
"""
|
|
43
|
+
self.data = array.array('h')
|
|
44
|
+
self.numchannels = 1
|
|
45
|
+
self.framerate = framerate
|
|
46
|
+
pygame.mixer.init(frequency=self.framerate, size=-16, channels=self.numchannels, allowedchanges=0)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def __getitem__(self, index):
|
|
51
|
+
"""
|
|
52
|
+
Get the sample at location index.
|
|
53
|
+
"""
|
|
54
|
+
return self.data[index]
|
|
55
|
+
|
|
56
|
+
def __len__(self):
|
|
57
|
+
"""
|
|
58
|
+
Support the len() function.
|
|
59
|
+
"""
|
|
60
|
+
return len(self.data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def __setitem__(self, index, value):
|
|
64
|
+
"""
|
|
65
|
+
Set the value of a particular sample.
|
|
66
|
+
"""
|
|
67
|
+
self.data[index] = value
|
|
68
|
+
|
|
69
|
+
def append(self, sample):
|
|
70
|
+
"""
|
|
71
|
+
Append one sample on to the end of the sound.
|
|
72
|
+
"""
|
|
73
|
+
self.data.append(sample)
|
|
74
|
+
|
|
75
|
+
def save(self, filename):
|
|
76
|
+
"""
|
|
77
|
+
Save the sound as 'filename' into the current working directory.
|
|
78
|
+
|
|
79
|
+
Under the hood, this is the point when the wav file is actually created
|
|
80
|
+
and the samples are written into it.
|
|
81
|
+
"""
|
|
82
|
+
snd = wave.open(filename, 'wb')
|
|
83
|
+
# nchannels, sampwidth, framerate, nframes, comptype, compname
|
|
84
|
+
snd.setparams((self.numchannels, 2, self.framerate, len(self.data), 'NONE', 'not compressed'))
|
|
85
|
+
snd.writeframes(self.data.tobytes())
|
|
86
|
+
snd.close()
|
|
87
|
+
|
|
88
|
+
def play(self):
|
|
89
|
+
"""
|
|
90
|
+
Play the sound.
|
|
91
|
+
"""
|
|
92
|
+
# Check if the data is empty. If it is, the backend would
|
|
93
|
+
# crash with an obscure message.
|
|
94
|
+
if len(self.data) == 0:
|
|
95
|
+
raise ValueError("Sound data empty. The sound should contain at least one sample")
|
|
96
|
+
sound = pygame.mixer.Sound(self.data)
|
|
97
|
+
sound.play()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def new(framerate=44100):
|
|
102
|
+
"""
|
|
103
|
+
A convenience function for creating a new Sound object.
|
|
104
|
+
"""
|
|
105
|
+
return Sound(framerate=framerate)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def open(filename):
|
|
109
|
+
"""
|
|
110
|
+
Open a wave file located in 'filename' (which is assumed to be in
|
|
111
|
+
the working directory, or an absolute path).
|
|
112
|
+
"""
|
|
113
|
+
soundObj = Sound()
|
|
114
|
+
|
|
115
|
+
snd = wave.open(filename, 'rb')
|
|
116
|
+
soundObj.numchannels = snd.getnchannels()
|
|
117
|
+
soundObj.framerate = snd.getframerate()
|
|
118
|
+
soundObj.data.frombytes(snd.readframes(snd.getnframes()))
|
|
119
|
+
|
|
120
|
+
snd.close()
|
|
121
|
+
return soundObj
|
|
122
|
+
|
|
123
|
+
def get_frequency(note):
|
|
124
|
+
"""
|
|
125
|
+
A quick converter for getting notes of various frequencies. This covers the
|
|
126
|
+
piano keyboard.
|
|
127
|
+
The sharp version of a, c, d, f, and g can be obtained using capital letters.
|
|
128
|
+
"""
|
|
129
|
+
d = {'a':27.5,'b':30.8677,'c':32.7032,'d':36.7081,'e':41.2035,'f':43.6536,'g':48.995,
|
|
130
|
+
'A':29.1353,'C':34.6479,'D':38.8909,'F':46.2493,'G':51.913}
|
|
131
|
+
frequency = d[note[0]]
|
|
132
|
+
octave = int(note[1])
|
|
133
|
+
if note[0] > 'b':
|
|
134
|
+
octave -= 1
|
|
135
|
+
frequency *= 2**octave
|
|
136
|
+
return frequency
|