cvf 1.0.0__py3-none-any.whl
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.
- cvf/__init__.py +297 -0
- cvf-1.0.0.dist-info/METADATA +185 -0
- cvf-1.0.0.dist-info/RECORD +6 -0
- cvf-1.0.0.dist-info/WHEEL +5 -0
- cvf-1.0.0.dist-info/licenses/LICENSE +21 -0
- cvf-1.0.0.dist-info/top_level.txt +1 -0
cvf/__init__.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cvf — Console Video File (.cvf) Making and Executing.
|
|
3
|
+
Author: Suleiman
|
|
4
|
+
Version: 1.0.0
|
|
5
|
+
License: MIT.
|
|
6
|
+
Copyright (c) Suleiman 2026
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
import lzma
|
|
13
|
+
import zipfile
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import cuifw
|
|
18
|
+
_HAVE_CUIFW = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
_HAVE_CUIFW = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__version__ = "1.0.0"
|
|
24
|
+
__author__ = "Suleiman"
|
|
25
|
+
__all__ = ["CreateVideo", "PlayVideo", "VideoData"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
DEFAULT_CHARS = " .:-=+*#%@"
|
|
29
|
+
MAGIC_FORMAT = "CVF"
|
|
30
|
+
FORMAT_VERSION = "1.0"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ═════════════════════════════════════════════════════════
|
|
34
|
+
# Writer
|
|
35
|
+
# ═════════════════════════════════════════════════════════
|
|
36
|
+
|
|
37
|
+
def _write_cvf(path, frames, width, height,
|
|
38
|
+
fps, chars, metadata, compress_level):
|
|
39
|
+
if not frames:
|
|
40
|
+
raise ValueError("cvf: no frames")
|
|
41
|
+
|
|
42
|
+
expected = width * height
|
|
43
|
+
for i, f in enumerate(frames):
|
|
44
|
+
if len(f) != expected:
|
|
45
|
+
raise ValueError(
|
|
46
|
+
"cvf: frame %d size %d, expected %d"
|
|
47
|
+
% (i, len(f), expected)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
raw = b"".join(frames)
|
|
51
|
+
compressed = lzma.compress(
|
|
52
|
+
raw, format=lzma.FORMAT_XZ, preset=int(compress_level)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
meta = {
|
|
56
|
+
"format": MAGIC_FORMAT,
|
|
57
|
+
"version": FORMAT_VERSION,
|
|
58
|
+
"width": int(width),
|
|
59
|
+
"height": int(height),
|
|
60
|
+
"fps": int(fps),
|
|
61
|
+
"frame_count": len(frames),
|
|
62
|
+
"chars": chars,
|
|
63
|
+
"compression": "lzma-xz",
|
|
64
|
+
"raw_size": len(raw),
|
|
65
|
+
"compressed_size": len(compressed),
|
|
66
|
+
"created": datetime.now().isoformat(),
|
|
67
|
+
}
|
|
68
|
+
if metadata:
|
|
69
|
+
meta["user"] = metadata
|
|
70
|
+
|
|
71
|
+
with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as z:
|
|
72
|
+
z.writestr("meta.json", json.dumps(meta, indent=2, ensure_ascii=False))
|
|
73
|
+
z.writestr("chars.txt", chars)
|
|
74
|
+
z.writestr("frames.lzma", compressed)
|
|
75
|
+
|
|
76
|
+
return path
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ═════════════════════════════════════════════════════════
|
|
80
|
+
# Reader
|
|
81
|
+
# ═════════════════════════════════════════════════════════
|
|
82
|
+
|
|
83
|
+
def _read_cvf(path, load_frames=True):
|
|
84
|
+
with zipfile.ZipFile(path, "r") as z:
|
|
85
|
+
meta = json.loads(z.read("meta.json").decode("utf-8"))
|
|
86
|
+
chars = z.read("chars.txt").decode("utf-8")
|
|
87
|
+
compressed = z.read("frames.lzma")
|
|
88
|
+
|
|
89
|
+
result = {
|
|
90
|
+
"meta": meta,
|
|
91
|
+
"chars": chars,
|
|
92
|
+
"frames": None,
|
|
93
|
+
"frame_count": meta.get("frame_count", 0),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if load_frames:
|
|
97
|
+
raw = lzma.decompress(compressed)
|
|
98
|
+
w = meta["width"]
|
|
99
|
+
h = meta["height"]
|
|
100
|
+
size = w * h
|
|
101
|
+
count = meta["frame_count"]
|
|
102
|
+
result["frames"] = [
|
|
103
|
+
raw[i * size:(i + 1) * size] for i in range(count)
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ═════════════════════════════════════════════════════════
|
|
110
|
+
# Player (CuiFW widget)
|
|
111
|
+
# ═════════════════════════════════════════════════════════
|
|
112
|
+
|
|
113
|
+
if _HAVE_CUIFW:
|
|
114
|
+
|
|
115
|
+
class _CVFWidget(cuifw.RenderWidget):
|
|
116
|
+
def __init__(self, x, y, w, h, chars, color, bg):
|
|
117
|
+
super().__init__(x, y, w, h)
|
|
118
|
+
self.chars = chars
|
|
119
|
+
self.color = color or cuifw.Colors.WHITE
|
|
120
|
+
self.bg = bg
|
|
121
|
+
self.frame = None
|
|
122
|
+
self.fw = 0
|
|
123
|
+
self.fh = 0
|
|
124
|
+
|
|
125
|
+
def set_frame(self, frame, w, h):
|
|
126
|
+
self.frame = frame
|
|
127
|
+
self.fw = w
|
|
128
|
+
self.fh = h
|
|
129
|
+
|
|
130
|
+
def clear_frame(self):
|
|
131
|
+
self.frame = None
|
|
132
|
+
self.fw = 0
|
|
133
|
+
self.fh = 0
|
|
134
|
+
|
|
135
|
+
def draw(self, canvas):
|
|
136
|
+
if self.frame is None:
|
|
137
|
+
return
|
|
138
|
+
w = min(self.fw, self.w)
|
|
139
|
+
h = min(self.fh, self.h)
|
|
140
|
+
nchars = len(self.chars)
|
|
141
|
+
for row in range(h):
|
|
142
|
+
base = row * self.fw
|
|
143
|
+
for col in range(w):
|
|
144
|
+
idx = self.frame[base + col]
|
|
145
|
+
if idx >= nchars:
|
|
146
|
+
idx = 0
|
|
147
|
+
ch = self.chars[idx]
|
|
148
|
+
if ch != " ":
|
|
149
|
+
canvas.set(self.x + col, self.y + row, ch,
|
|
150
|
+
self.color, self.bg)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class _CVFPlayer:
|
|
154
|
+
def __init__(self, path, color=None, bg=None,
|
|
155
|
+
show_status=True, loop=False):
|
|
156
|
+
data = _read_cvf(path, load_frames=True)
|
|
157
|
+
self.path = path
|
|
158
|
+
self.meta = data["meta"]
|
|
159
|
+
self.chars = data["chars"]
|
|
160
|
+
self.frames = data["frames"]
|
|
161
|
+
self.width = self.meta["width"]
|
|
162
|
+
self.height = self.meta["height"]
|
|
163
|
+
self.fps = self.meta["fps"] or 15
|
|
164
|
+
self.frame_time = 1.0 / self.fps
|
|
165
|
+
self.loop = loop
|
|
166
|
+
self.show_status = show_status
|
|
167
|
+
|
|
168
|
+
self.app = cuifw.App(alt_screen=True)
|
|
169
|
+
self.W, self.H = self.app.size()
|
|
170
|
+
h = self.H - 1 if show_status else self.H
|
|
171
|
+
|
|
172
|
+
self.widget = _CVFWidget(0, 0, self.W, h,
|
|
173
|
+
chars=self.chars,
|
|
174
|
+
color=color, bg=bg)
|
|
175
|
+
self.app.add(self.widget)
|
|
176
|
+
|
|
177
|
+
self.index = 0
|
|
178
|
+
self.last = 0.0
|
|
179
|
+
self.shown = 0
|
|
180
|
+
self.start = 0.0
|
|
181
|
+
|
|
182
|
+
def _draw_status(self):
|
|
183
|
+
if not self.show_status:
|
|
184
|
+
return
|
|
185
|
+
canvas = self.app.canvas
|
|
186
|
+
if canvas is None:
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
elapsed = time.time() - self.start
|
|
190
|
+
fps = self.shown / elapsed if elapsed > 0 else 0
|
|
191
|
+
total = len(self.frames)
|
|
192
|
+
name = os.path.basename(self.path)
|
|
193
|
+
|
|
194
|
+
text = " %s | %d/%d | %.1f FPS " % (name, self.index, total, fps)
|
|
195
|
+
|
|
196
|
+
y = self.H - 1
|
|
197
|
+
canvas.fill_rect(0, y, self.W - 1, y, " ",
|
|
198
|
+
cuifw.Colors.BLACK, cuifw.Colors.BG_BLACK)
|
|
199
|
+
canvas.text(0, y, text[:self.W],
|
|
200
|
+
cuifw.Colors.CYAN, cuifw.Colors.BG_BLACK)
|
|
201
|
+
|
|
202
|
+
def _on_tick(self):
|
|
203
|
+
now = time.time()
|
|
204
|
+
if now - self.last < self.frame_time:
|
|
205
|
+
return
|
|
206
|
+
self.last = now
|
|
207
|
+
|
|
208
|
+
if self.index >= len(self.frames):
|
|
209
|
+
if self.loop:
|
|
210
|
+
self.index = 0
|
|
211
|
+
else:
|
|
212
|
+
self.app.quit()
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
frame = self.frames[self.index]
|
|
216
|
+
self.widget.set_frame(frame, self.width, self.height)
|
|
217
|
+
self.index += 1
|
|
218
|
+
self.shown += 1
|
|
219
|
+
|
|
220
|
+
self.app.redraw()
|
|
221
|
+
self._draw_status()
|
|
222
|
+
|
|
223
|
+
def run(self):
|
|
224
|
+
self.start = time.time()
|
|
225
|
+
try:
|
|
226
|
+
self.app.run(on_tick=self._on_tick, tick_interval=0.005)
|
|
227
|
+
except KeyboardInterrupt:
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ═════════════════════════════════════════════════════════
|
|
232
|
+
# Public API
|
|
233
|
+
# ═════════════════════════════════════════════════════════
|
|
234
|
+
|
|
235
|
+
def CreateVideo(path, frames, width, height,
|
|
236
|
+
fps=15, chars=DEFAULT_CHARS,
|
|
237
|
+
metadata=None, compress_level=6):
|
|
238
|
+
"""
|
|
239
|
+
Create a .cvf video file.
|
|
240
|
+
|
|
241
|
+
path — output .cvf path
|
|
242
|
+
frames — list of bytes (each width*height)
|
|
243
|
+
width, height — frame dimensions
|
|
244
|
+
fps — playback framerate
|
|
245
|
+
chars — character palette (max 256)
|
|
246
|
+
metadata — optional dict of extra info
|
|
247
|
+
compress_level — LZMA 0-9 (default 6)
|
|
248
|
+
|
|
249
|
+
Returns path.
|
|
250
|
+
"""
|
|
251
|
+
return _write_cvf(path, frames, width, height,
|
|
252
|
+
fps=fps, chars=chars,
|
|
253
|
+
metadata=metadata,
|
|
254
|
+
compress_level=compress_level)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def PlayVideo(path, color=None, bg=None,
|
|
258
|
+
show_status=True, loop=False):
|
|
259
|
+
"""
|
|
260
|
+
Play a .cvf file in the terminal using CuiFW.
|
|
261
|
+
|
|
262
|
+
path — input .cvf file
|
|
263
|
+
color — ANSI color (default: WHITE)
|
|
264
|
+
bg — background color (default: none)
|
|
265
|
+
show_status — show filename + FPS at bottom
|
|
266
|
+
loop — loop playback
|
|
267
|
+
|
|
268
|
+
Requires CuiFW. Raises RuntimeError if CuiFW is not installed.
|
|
269
|
+
|
|
270
|
+
Returns the player object.
|
|
271
|
+
"""
|
|
272
|
+
if not _HAVE_CUIFW:
|
|
273
|
+
raise RuntimeError(
|
|
274
|
+
"cvf: CuiFW is required for PlayVideo(). "
|
|
275
|
+
"Install CuiFW or use VideoData() to read frames manually."
|
|
276
|
+
)
|
|
277
|
+
player = _CVFPlayer(path, color=color, bg=bg,
|
|
278
|
+
show_status=show_status, loop=loop)
|
|
279
|
+
player.run()
|
|
280
|
+
return player
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def VideoData(path, load_frames=True):
|
|
284
|
+
"""
|
|
285
|
+
Read a .cvf file.
|
|
286
|
+
|
|
287
|
+
Returns dict:
|
|
288
|
+
{
|
|
289
|
+
"meta": {...},
|
|
290
|
+
"chars": "...",
|
|
291
|
+
"frames": [bytes, ...] or None,
|
|
292
|
+
"frame_count": int,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
Set load_frames=False to read only metadata.
|
|
296
|
+
"""
|
|
297
|
+
return _read_cvf(path, load_frames=load_frames)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cvf
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Console Video File - ASCII video format for the terminal
|
|
5
|
+
Home-page:
|
|
6
|
+
Author: Suleiman
|
|
7
|
+
Author-email:
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: terminal ascii video cui cvf cuifw
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Multimedia :: Video
|
|
22
|
+
Classifier: Topic :: Terminals
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Requires-Python: >=3.7
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: cuifw==1.7.0
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: classifier
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: keywords
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
Dynamic: requires-dist
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
Dynamic: summary
|
|
38
|
+
|
|
39
|
+
# cvf
|
|
40
|
+
|
|
41
|
+
Console Video File — ASCII video format for the terminal.
|
|
42
|
+
|
|
43
|
+
Stores ASCII frames in a ZIP container with JSON metadata and LZMA-compressed
|
|
44
|
+
frames. Playback uses CuiFW to render frames in the terminal.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- JSON metadata
|
|
49
|
+
- LZMA-compressed frames
|
|
50
|
+
- ZIP container
|
|
51
|
+
- No ffmpeg for playback
|
|
52
|
+
- Stdlib only
|
|
53
|
+
- Windows, Linux, macOS, Termux
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
pip install cvf
|
|
58
|
+
|
|
59
|
+
Optional, for playback:
|
|
60
|
+
|
|
61
|
+
pip install cuifw
|
|
62
|
+
|
|
63
|
+
## API
|
|
64
|
+
|
|
65
|
+
import cvf
|
|
66
|
+
|
|
67
|
+
cvf.CreateVideo(path, frames, width, height, fps=15, chars=..., metadata=None, compress_level=6)
|
|
68
|
+
cvf.PlayVideo(path, color=None, bg=None, show_status=True, loop=False)
|
|
69
|
+
cvf.VideoData(path, load_frames=True)
|
|
70
|
+
|
|
71
|
+
### CreateVideo
|
|
72
|
+
|
|
73
|
+
Write a .cvf file.
|
|
74
|
+
|
|
75
|
+
import cvf
|
|
76
|
+
|
|
77
|
+
frames = [...] # list of bytes, each width*height
|
|
78
|
+
cvf.CreateVideo("out.cvf", frames, width=80, height=24, fps=15)
|
|
79
|
+
|
|
80
|
+
Returns the output path.
|
|
81
|
+
|
|
82
|
+
### PlayVideo
|
|
83
|
+
|
|
84
|
+
Play a .cvf file using CuiFW. Requires CuiFW installed.
|
|
85
|
+
|
|
86
|
+
import cvf
|
|
87
|
+
|
|
88
|
+
cvf.PlayVideo("out.cvf")
|
|
89
|
+
|
|
90
|
+
Returns the player object.
|
|
91
|
+
|
|
92
|
+
### VideoData
|
|
93
|
+
|
|
94
|
+
Read a .cvf file.
|
|
95
|
+
|
|
96
|
+
import cvf
|
|
97
|
+
|
|
98
|
+
data = cvf.VideoData("out.cvf")
|
|
99
|
+
print(data["meta"]["fps"])
|
|
100
|
+
print(data["frame_count"])
|
|
101
|
+
|
|
102
|
+
for frame in data["frames"]:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
Metadata only:
|
|
106
|
+
|
|
107
|
+
data = cvf.VideoData("out.cvf", load_frames=False)
|
|
108
|
+
|
|
109
|
+
## File format
|
|
110
|
+
|
|
111
|
+
A .cvf file is a standard ZIP archive:
|
|
112
|
+
|
|
113
|
+
file.cvf
|
|
114
|
+
├── meta.json
|
|
115
|
+
├── chars.txt
|
|
116
|
+
└── frames.lzma
|
|
117
|
+
|
|
118
|
+
### meta.json
|
|
119
|
+
|
|
120
|
+
{
|
|
121
|
+
"format": "CVF",
|
|
122
|
+
"version": "1.0",
|
|
123
|
+
"width": 80,
|
|
124
|
+
"height": 24,
|
|
125
|
+
"fps": 15,
|
|
126
|
+
"frame_count": 450,
|
|
127
|
+
"chars": " .:-=+*#%@",
|
|
128
|
+
"compression": "lzma-xz",
|
|
129
|
+
"raw_size": 864000,
|
|
130
|
+
"compressed_size": 312450,
|
|
131
|
+
"created": "2026-10-07T12:34:56"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
### chars.txt
|
|
135
|
+
|
|
136
|
+
Character palette. Each byte in the frame data is an index into this string.
|
|
137
|
+
|
|
138
|
+
Default:
|
|
139
|
+
|
|
140
|
+
.:-=+*#%@
|
|
141
|
+
|
|
142
|
+
### frames.lzma
|
|
143
|
+
|
|
144
|
+
All frames concatenated, then compressed with LZMA (XZ).
|
|
145
|
+
|
|
146
|
+
Frame size = width * height bytes.
|
|
147
|
+
Total raw size = width * height * frame_count bytes.
|
|
148
|
+
|
|
149
|
+
## Size
|
|
150
|
+
|
|
151
|
+
80x24, 15 FPS, 10s raw 288 KB cvf ~110 KB
|
|
152
|
+
80x24, 15 FPS, 60s raw 1.7 MB cvf ~600 KB
|
|
153
|
+
120x40, 15 FPS, 60s raw 4.3 MB cvf ~1.4 MB
|
|
154
|
+
|
|
155
|
+
## Requirements
|
|
156
|
+
|
|
157
|
+
- Python 3.7+
|
|
158
|
+
- Stdlib only
|
|
159
|
+
- Optional: CuiFW for playback
|
|
160
|
+
|
|
161
|
+
## Limitations
|
|
162
|
+
|
|
163
|
+
- ASCII only
|
|
164
|
+
- Fixed frame size
|
|
165
|
+
- No audio
|
|
166
|
+
- Fixed FPS
|
|
167
|
+
- No seek during playback
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT — see LICENSE.
|
|
172
|
+
|
|
173
|
+
## Author
|
|
174
|
+
|
|
175
|
+
Suleiman
|
|
176
|
+
|
|
177
|
+
## Changelog
|
|
178
|
+
|
|
179
|
+
### 1.0.0
|
|
180
|
+
|
|
181
|
+
- Initial release
|
|
182
|
+
- CreateVideo, PlayVideo, VideoData
|
|
183
|
+
- JSON metadata
|
|
184
|
+
- LZMA-compressed frames
|
|
185
|
+
- ZIP container
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
cvf/__init__.py,sha256=g2_KfULX7O86x7V-Vqgb4DvRTGZawBF1P3wNI5IyJA4,9517
|
|
2
|
+
cvf-1.0.0.dist-info/licenses/LICENSE,sha256=XABCXMEhX5-sSW6dVui3V35LHqtlwNF4Rj0Rfhuo4_8,1064
|
|
3
|
+
cvf-1.0.0.dist-info/METADATA,sha256=o0u1Ma8AnmNRGg4u2hqSX9aI8bM_CcjjYZ9DPvzqyD8,3788
|
|
4
|
+
cvf-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
cvf-1.0.0.dist-info/top_level.txt,sha256=naqdI6HXCxjbrjA_gr-Yg4TJLsPhkV-O9jDZarmgLOE,4
|
|
6
|
+
cvf-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Suleiman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cvf
|