ulf 0.1.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.
ulf/__init__.py ADDED
@@ -0,0 +1,116 @@
1
+ """Python API for Unicode Lossful Format (ULF).
2
+
3
+ The functions in this module expose the same settings as the command-line
4
+ tools. Optional features need their matching extra: ``ulf[all]`` installs
5
+ GPU playback/matching and live audio playback support.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from os import PathLike
11
+ from typing import Literal
12
+
13
+ __version__ = "0.1.0"
14
+
15
+ Pathish = str | PathLike[str]
16
+ Backend = Literal["auto", "vulkan", "cpu"]
17
+ Quality = Literal["high", "medium", "low"]
18
+ Encoder = Literal["auto", "nvenc", "amf", "qsv", "vaapi", "cpu"]
19
+
20
+
21
+ def _path(value: Pathish) -> str:
22
+ return str(value)
23
+
24
+
25
+ def encode(
26
+ input: Pathish,
27
+ output: Pathish | None = None,
28
+ *,
29
+ size: str = "80x40",
30
+ fps: float = 0,
31
+ font: Pathish | None = None,
32
+ every: int = 1,
33
+ backend: Backend = "auto",
34
+ keyframe_interval: int = 24,
35
+ emojiless: bool = False,
36
+ hackerman: bool = False,
37
+ audio_waves: int = 8,
38
+ audio_segment: float = 0.05,
39
+ mute: bool = False,
40
+ debug: bool = False,
41
+ ) -> None:
42
+ """Encode a video as ULF.
43
+
44
+ ``output`` defaults to the input filename with a ``.ulf`` suffix.
45
+ Keyword names correspond to the CLI options with hyphens replaced by
46
+ underscores.
47
+ """
48
+ from pathlib import Path
49
+ from ulf_encode import main
50
+
51
+ source = _path(input)
52
+ destination = _path(output) if output is not None else str(Path(source).with_suffix(".ulf"))
53
+ argv = [source, destination, "--size", size, "--fps", str(fps), "--every", str(every),
54
+ "--backend", backend, "--keyframe-interval", str(keyframe_interval),
55
+ "--audio-waves", str(audio_waves), "--audio-segment", str(audio_segment)]
56
+ if font is not None:
57
+ argv += ["--font", _path(font)]
58
+ if emojiless:
59
+ argv.append("--emojiless")
60
+ if hackerman:
61
+ argv.append("--hackerman")
62
+ if mute:
63
+ argv.append("--mute")
64
+ if debug:
65
+ argv.append("--debug")
66
+ main(argv)
67
+
68
+
69
+ def play(
70
+ input: Pathish,
71
+ *,
72
+ window: str = "1000x700",
73
+ font: Pathish | None = None,
74
+ backend: Backend = "auto",
75
+ mute: bool = False,
76
+ debug: bool = False,
77
+ ) -> None:
78
+ """Play a ULF file in a window."""
79
+ from ulf_play import main
80
+
81
+ argv = [_path(input), "--window", window, "--backend", backend]
82
+ if font is not None:
83
+ argv += ["--font", _path(font)]
84
+ if mute:
85
+ argv.append("--mute")
86
+ if debug:
87
+ argv.append("--debug")
88
+ main(argv)
89
+
90
+
91
+ def convert(
92
+ input: Pathish,
93
+ output: Pathish,
94
+ *,
95
+ font: Pathish | None = None,
96
+ backend: Backend = "auto",
97
+ mute: bool = False,
98
+ export_size: str | None = None,
99
+ export_quality: Quality = "high",
100
+ encoder: Encoder = "auto",
101
+ ) -> None:
102
+ """Export a ULF file to MP4 using FFmpeg."""
103
+ from ulf_play import main
104
+
105
+ argv = [_path(input), "--backend", backend, "--export", _path(output),
106
+ "--export-quality", export_quality, "--encoder", encoder]
107
+ if font is not None:
108
+ argv += ["--font", _path(font)]
109
+ if mute:
110
+ argv.append("--mute")
111
+ if export_size is not None:
112
+ argv += ["--export-size", export_size]
113
+ main(argv)
114
+
115
+
116
+ __all__ = ["convert", "encode", "play", "__version__"]
ulf/__main__.py ADDED
@@ -0,0 +1,39 @@
1
+ """Unified ``ulf`` command line entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+
8
+ def main(argv=None) -> None:
9
+ parser = argparse.ArgumentParser(prog="ulf", description="Unicode Lossful Format tools")
10
+ commands = parser.add_subparsers(dest="command", required=True)
11
+ # Disable subcommand help here so it is forwarded to the established
12
+ # encoder/player parsers, which own the option documentation.
13
+ commands.add_parser("encode", help="Encode video as ULF", add_help=False)
14
+ commands.add_parser("play", help="Play a ULF file", add_help=False)
15
+ convert_parser = commands.add_parser(
16
+ "convert", help="Export a ULF file to MP4", add_help=False
17
+ )
18
+ convert_parser.add_argument("input", nargs="?")
19
+ convert_parser.add_argument("output", nargs="?")
20
+ namespace, remaining = parser.parse_known_args(argv)
21
+
22
+ if namespace.command == "encode":
23
+ from ulf_encode import main as command
24
+ command(remaining)
25
+ elif namespace.command == "play":
26
+ from ulf_play import main as command
27
+ command(remaining)
28
+ else:
29
+ from ulf_play import main as command
30
+ if "-h" in remaining or "--help" in remaining:
31
+ command(["--help"])
32
+ return
33
+ if namespace.input is None or namespace.output is None:
34
+ convert_parser.error("the following arguments are required: input, output")
35
+ command([namespace.input, "--export", namespace.output, *remaining])
36
+
37
+
38
+ if __name__ == "__main__":
39
+ main()
ulf/_optional.py ADDED
@@ -0,0 +1,23 @@
1
+
2
+ """Checks and messages for features supplied by optional dependencies."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import importlib.util
7
+ import sys
8
+
9
+ INSTALL_ALL = 'python -m pip install "unicode-lossful-format[all]"'
10
+
11
+
12
+ def has_optional_dependencies(*modules: str) -> bool:
13
+ """Return True when all requested optional modules are available."""
14
+ return all(importlib.util.find_spec(module) is not None for module in modules)
15
+
16
+
17
+ def warn_optional(feature: str) -> None:
18
+ """Tell users how to enable an optional enhancement."""
19
+ print(
20
+ f"WARNING: {feature} will not work without optional dependencies. "
21
+ f"Install every optional feature with: {INSTALL_ALL}",
22
+ file=sys.stderr,
23
+ )
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: ulf
3
+ Version: 0.1.0
4
+ Summary: Encode, play, and export Unicode Lossful Format video files.
5
+ Author: ULF contributors
6
+ License-Expression: GPL-3.0-only
7
+ Project-URL: Homepage, https://github.com/EquinoxDaFurry/ULF
8
+ Project-URL: Repository, https://github.com/EquinoxDaFurry/ULF
9
+ Project-URL: Issues, https://github.com/EquinoxDaFurry/ULF/issues
10
+ Keywords: video,unicode,encoder,terminal,ulf
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: fonttools
23
+ Requires-Dist: numpy
24
+ Requires-Dist: opencv-python
25
+ Requires-Dist: pillow
26
+ Requires-Dist: regex
27
+ Provides-Extra: gpu
28
+ Requires-Dist: wgpu; extra == "gpu"
29
+ Requires-Dist: rendercanvas; extra == "gpu"
30
+ Requires-Dist: glfw; extra == "gpu"
31
+ Provides-Extra: audio
32
+ Requires-Dist: sounddevice; extra == "audio"
33
+ Provides-Extra: all
34
+ Requires-Dist: wgpu; extra == "all"
35
+ Requires-Dist: rendercanvas; extra == "all"
36
+ Requires-Dist: glfw; extra == "all"
37
+ Requires-Dist: sounddevice; extra == "all"
38
+ Dynamic: license-file
39
+
40
+ # Unicode Lossful Format (ULF)
41
+
42
+ A deliberately silly text-based video format. ULF represents video frames using Unicode characters, choosing glyphs that visually resemble parts of the original image.
43
+
44
+ It supports audio, delta-frame compression, GPU acceleration, and MP4 export.
45
+
46
+ ## Comparison
47
+
48
+ <img src="ULF_comparison.gif" alt="ULF Comparison" width="600">
49
+
50
+ ## Requirements
51
+
52
+ Python 3.10+.
53
+
54
+ Install the package (with all optional playback features):
55
+
56
+ ```bash
57
+ python -m pip install "unicode-lossful-format[all]"
58
+ ```
59
+
60
+ FFmpeg must be installed and available on your system PATH for MP4 export.
61
+ It is an external system dependency and is not installed by pip or by any ULF
62
+ extra. Verify it with `ffmpeg -version` before using `ulf convert` or
63
+ `ulf.convert()`.
64
+
65
+ ## Encode
66
+
67
+ ```bash
68
+ ulf-encode input.mp4 output.ulf --size 80x40
69
+ ```
70
+
71
+ ### Options
72
+
73
+ - `--size WIDTHxHEIGHT`: Character-grid resolution (default: `80x40`).
74
+ - `--fps N`: Output frame rate.
75
+ - `--font PATH`: Font used for glyph matching.
76
+ - `--every N`: Encode every Nth source frame.
77
+ - `--backend auto|vulkan|cpu`: Select processing backend.
78
+ - `--keyframe-interval N`: Set the interval between full frames.
79
+ - `--mute`: Disable audio.
80
+ - `--audio-waves N`: Set audio reconstruction detail.
81
+ - `--emojiless`: Exclude emoji characters.
82
+ - `--hackerman`: Use printable keyboard ASCII characters only.
83
+ - `--debug`: Enable diagnostic output.
84
+
85
+ ## Play
86
+
87
+ ```bash
88
+ ulf-play output.ulf
89
+ ```
90
+
91
+ ### Options
92
+
93
+ - `--window WIDTHxHEIGHT`: Set playback window size.
94
+ - `--font PATH`: Select playback font.
95
+ - `--backend auto|vulkan|cpu`: Select playback backend.
96
+ - `--mute`: Disable audio.
97
+ - `--debug`: Enable diagnostic output.
98
+
99
+ ## Convert to MP4
100
+
101
+ ```bash
102
+ ulf convert output.ulf output.mp4 --encoder auto --export-quality high
103
+ ```
104
+
105
+ The legacy equivalent remains `ulf-play output.ulf --export output.mp4`.
106
+
107
+ ## Python API
108
+
109
+ The same options are available as keyword arguments, using underscores in
110
+ place of CLI hyphens:
111
+
112
+ ```python
113
+ import ulf
114
+
115
+ ulf.encode("example.mp4", "example.ulf", size="80x40", backend="auto")
116
+ ulf.play("example.ulf", window="1280x720", mute=False)
117
+ ulf.convert("example.ulf", "example.mp4", encoder="auto", export_quality="high")
118
+ ```
119
+
120
+ `encode()` derives an `.ulf` output path when its `output` argument is omitted.
121
+ `convert()` requires FFmpeg on your system `PATH`, just like the CLI.
122
+
123
+ ## File format
124
+
125
+ ULF files use the `.ulf` extension and are stored as UTF-8 text.
126
+
127
+ The format uses the `ULF/1.0` signature and supports full-frame and delta-frame video, with optional audio data.
128
+
129
+ ## Limitations
130
+
131
+ - Video is grayscale.
132
+ - Audio reconstruction is lossy.
133
+ - Visual quality depends on the selected font and renderer.
134
+ - Vulkan requires compatible hardware and drivers.
135
+ - MP4 export requires FFmpeg.
136
+ - Large resolutions can increase processing time and file size.
137
+
138
+ ## Have fun
139
+
140
+ ULF makes video less efficient on purpose.
141
+
142
+ Now with compression, GPU acceleration, sound, and MP4 export!
@@ -0,0 +1,11 @@
1
+ ulf_encode.py,sha256=QN16ruYiV0V7WOd-3Cdv5LK_JXPdltUpCa0T_BDnzU8,33888
2
+ ulf_play.py,sha256=I5_2eXmq4rIlBgUQ_aKDuIqHm5SY927UZ_drsy-unGg,57224
3
+ ulf/__init__.py,sha256=rg_IwHyWM_-jvwonivtRGOiat_UVWRYrUOxl2aJKL-o,3189
4
+ ulf/__main__.py,sha256=H4GnPXVM8o_O0CXuAA0CeIxq3lnyLL3KNoQBKn4ACgs,1512
5
+ ulf/_optional.py,sha256=6hJVgK50liDsj7YMuZYMOdaU4bU4KW_ymRN4KipIWgg,710
6
+ ulf-0.1.0.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
7
+ ulf-0.1.0.dist-info/METADATA,sha256=u1qqJvnKnLDSsXGg_orW5iGHr9K4OBpGaLbUtuqdzs4,4487
8
+ ulf-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
9
+ ulf-0.1.0.dist-info/entry_points.txt,sha256=BjmilIMsQTtvd0hzEw8dDkBItyeCvBcg-Y3yq1U0b1s,96
10
+ ulf-0.1.0.dist-info/top_level.txt,sha256=yiDrSbNvzVHvoxW6gsxkz_1ZJ0vhZneEpvTGxsZan3c,24
11
+ ulf-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ ulf = ulf.__main__:main
3
+ ulf-encode = ulf_encode:main
4
+ ulf-play = ulf_play:main