siss 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.
siss-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Michail Semoglou
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.
siss-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: siss
3
+ Version: 0.1.0
4
+ Summary: A command-line utility for applying artistic effects to videos
5
+ Home-page: https://github.com/MichailSemoglou/siss
6
+ Author: Michail Semoglou
7
+ Author-email: m.semoglou@tongji.edu.cn
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Multimedia :: Video
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.6
13
+ Classifier: Programming Language :: Python :: 3.7
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Requires-Python: >=3.6
18
+ License-File: LICENSE
19
+ Requires-Dist: opencv-python>=4.5.0
20
+ Requires-Dist: numpy>=1.20.0
21
+ Requires-Dist: tqdm>=4.60.0
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: home-page
26
+ Dynamic: license-file
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
siss-0.1.0/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # Siss
2
+
3
+ A command-line utility for applying artistic effects to videos.
4
+
5
+ ![GitHub license](https://img.shields.io/github/license/MichailSemoglou/siss)
6
+ ![Python version](https://img.shields.io/badge/python-3.6%2B-blue)
7
+
8
+ ## Features
9
+
10
+ - **Duotone Effect**: Creates a video with two selected colors mapped to dark and light areas
11
+ - **Halftone Effect**: Creates a video with symbol patterns of varying sizes to represent dark and light areas
12
+ - **Cross-platform Compatibility**: Handles codec differences between operating systems
13
+ - **Progress Tracking**: Shows real-time progress during video processing
14
+
15
+ ## Installation
16
+
17
+ ### Option 1: Clone and Install
18
+
19
+ 1. Clone this repository:
20
+
21
+ ```bash
22
+ git clone https://github.com/MichailSemoglou/siss.git
23
+ cd siss
24
+ ```
25
+
26
+ 2. Create a virtual environment (recommended):
27
+ ```bash
28
+ python -m venv venv
29
+ source venv/bin/activate # On Windows: venv\Scripts\activate
30
+ ```
31
+
32
+ 3. Install the required dependencies:
33
+ ```bash
34
+ pip install -r requirements.txt
35
+ ```
36
+
37
+ ### Option 2: Install from Source
38
+
39
+ ```bash
40
+ pip install git+https://github.com/MichailSemoglou/siss.git
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### Basic Usage
46
+
47
+ ```bash
48
+ python -m src.main input_video.mp4 output_video.mp4 --effect duotone
49
+ ```
50
+
51
+ Or if installed using pip:
52
+
53
+ ```bash
54
+ siss input_video.mp4 output_video.mp4 --effect duotone
55
+ ```
56
+
57
+ The tool supports various video formats including MP4, MOV, AVI, and more. The output format is determined by the file extension you specify for the output file.
58
+
59
+ ### Duotone Effect
60
+
61
+ ```bash
62
+ python -m src.main input_video.mp4 output_duotone.mp4 --effect duotone --color1 255 0 0 --color2 0 255 255
63
+ ```
64
+
65
+ This applies a duotone effect with red for dark areas and cyan for light areas.
66
+
67
+ ### Halftone Effect
68
+
69
+ ```bash
70
+ python -m src.main input_video.mp4 output_halftone.mp4 --effect halftone --symbol_size 12 --symbol_type asterisk --color1 0 0 0 --color2 255 255 255
71
+ ```
72
+
73
+ This applies a halftone effect with black asterisks on a white background.
74
+
75
+ ### Codec Compatibility Fix
76
+
77
+ If you encounter issues with codecs (especially on different operating systems), use the `--use-codec-fix` option:
78
+
79
+ ```bash
80
+ python -m src.main input_video.mp4 output_video.mp4 --effect duotone --use-codec-fix
81
+ ```
82
+
83
+ This option uses an adaptive approach to find compatible codecs for your specific system.
84
+
85
+ ### Available Options
86
+
87
+ - `--effect`: Choose between `duotone` or `halftone` (required)
88
+ - `--color1`: First color in RGB format (default: 255 0 0, red)
89
+ - `--color2`: Second color in RGB format (default: 0 255 255, cyan)
90
+ - `--symbol_size`: Size of symbols for halftone effect (default: 10)
91
+ - `--symbol_type`: Type of symbol for halftone effect (choices: plus, asterisk, slash, default: plus)
92
+ - `--use-codec-fix`: Use adaptive codec selection for cross-platform compatibility
93
+
94
+ ## Examples
95
+
96
+ ### Creating a blue/yellow duotone effect:
97
+
98
+ ```bash
99
+ python -m src.main video.mp4 blue_yellow.mp4 --effect duotone --color1 0 0 255 --color2 255 255 0
100
+ ```
101
+
102
+ ### Creating a halftone effect with slash symbols:
103
+
104
+ ```bash
105
+ python -m src.main video.mp4 halftone_slashes.mp4 --effect halftone --symbol_type slash --symbol_size 15
106
+ ```
107
+
108
+ ### Using MOV files:
109
+
110
+ ```bash
111
+ python -m src.main input.mov output.mov --effect duotone --color1 0 0 255 --color2 255 255 0
112
+ ```
113
+
114
+ ## Project Structure
115
+
116
+ - `src/`
117
+ - `main.py`: Command-line interface for the tool
118
+ - `duotone.py`: Contains the duotone effect implementation
119
+ - `halftone.py`: Contains the halftone effect implementation
120
+ - `codec_fix.py`: Handles cross-platform codec compatibility
121
+ - `utils/`
122
+ - `video_processing.py`: Utility functions for video handling
123
+
124
+ ## Requirements
125
+
126
+ - Python 3.6+
127
+ - OpenCV (cv2)
128
+ - NumPy
129
+ - tqdm
130
+
131
+ ## Troubleshooting
132
+
133
+ ### Video Output Issues
134
+
135
+ If you encounter issues with video output:
136
+
137
+ 1. Try using the `--use-codec-fix` option to automatically find a compatible codec
138
+ 2. Check that you have the necessary codecs installed for your operating system
139
+ 3. If creating MP4 files on Windows, try using AVI format instead
140
+
141
+ ### Memory Limitations
142
+
143
+ For large videos, the tool processes frames sequentially to minimize memory usage. If you still experience memory issues:
144
+
145
+ 1. Try processing a shorter clip first
146
+ 2. Reduce the resolution of your input video
147
+
148
+ ## Contributing
149
+
150
+ Contributions are welcome! Please feel free to submit a Pull Request.
151
+
152
+ 1. Fork the repository
153
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
154
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
155
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
156
+ 5. Open a Pull Request
157
+
158
+ ## License
159
+
160
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
siss-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
siss-0.1.0/setup.py ADDED
@@ -0,0 +1,34 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="siss",
5
+ version="0.1.0",
6
+ description="A command-line utility for applying artistic effects to videos",
7
+ author="Michail Semoglou",
8
+ author_email="m.semoglou@tongji.edu.cn",
9
+ url="https://github.com/MichailSemoglou/siss",
10
+ packages=find_packages(where="src"),
11
+ package_dir={"": "src"},
12
+ install_requires=[
13
+ "opencv-python>=4.5.0",
14
+ "numpy>=1.20.0",
15
+ "tqdm>=4.60.0",
16
+ ],
17
+ python_requires=">=3.6",
18
+ entry_points={
19
+ "console_scripts": [
20
+ "siss=src.main:main",
21
+ ],
22
+ },
23
+ classifiers=[
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Developers",
26
+ "Topic :: Multimedia :: Video",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.6",
29
+ "Programming Language :: Python :: 3.7",
30
+ "Programming Language :: Python :: 3.8",
31
+ "Programming Language :: Python :: 3.9",
32
+ "License :: OSI Approved :: MIT License",
33
+ ],
34
+ )
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: siss
3
+ Version: 0.1.0
4
+ Summary: A command-line utility for applying artistic effects to videos
5
+ Home-page: https://github.com/MichailSemoglou/siss
6
+ Author: Michail Semoglou
7
+ Author-email: m.semoglou@tongji.edu.cn
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Multimedia :: Video
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.6
13
+ Classifier: Programming Language :: Python :: 3.7
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Requires-Python: >=3.6
18
+ License-File: LICENSE
19
+ Requires-Dist: opencv-python>=4.5.0
20
+ Requires-Dist: numpy>=1.20.0
21
+ Requires-Dist: tqdm>=4.60.0
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: home-page
26
+ Dynamic: license-file
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ src/siss.egg-info/PKG-INFO
5
+ src/siss.egg-info/SOURCES.txt
6
+ src/siss.egg-info/dependency_links.txt
7
+ src/siss.egg-info/entry_points.txt
8
+ src/siss.egg-info/requires.txt
9
+ src/siss.egg-info/top_level.txt
10
+ src/utils/__init__.py
11
+ src/utils/video_processing.py
12
+ tests/test_duotone.py
13
+ tests/test_halftone.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ siss = src.main:main
@@ -0,0 +1,3 @@
1
+ opencv-python>=4.5.0
2
+ numpy>=1.20.0
3
+ tqdm>=4.60.0
@@ -0,0 +1 @@
1
+ utils
@@ -0,0 +1,3 @@
1
+ """
2
+ Utility modules for Siss.
3
+ """
@@ -0,0 +1,226 @@
1
+ """
2
+ Utility functions for video processing operations.
3
+
4
+ This module provides helper functions for common video operations like
5
+ loading videos, extracting frames, and saving processed results.
6
+ """
7
+ import cv2
8
+ import numpy as np
9
+ import os
10
+ from tqdm import tqdm
11
+
12
+
13
+ def get_codec_for_file(output_path):
14
+ """
15
+ Determine the appropriate codec based on the file extension.
16
+
17
+ Args:
18
+ output_path (str): Path where the video will be saved
19
+
20
+ Returns:
21
+ str: Four character codec code
22
+ """
23
+ # Get file extension (lowercase)
24
+ _, ext = os.path.splitext(output_path)
25
+ ext = ext.lower()
26
+
27
+ # Map extensions to codecs
28
+ codec_map = {
29
+ '.avi': 'XVID',
30
+ '.mp4': 'mp4v', # H.264 codec
31
+ '.mov': 'mp4v', # H.264 codec for MOV container
32
+ '.mkv': 'X264',
33
+ '.wmv': 'WMV2',
34
+ }
35
+
36
+ # Default to mp4v if extension not found
37
+ return codec_map.get(ext, 'mp4v')
38
+
39
+
40
+ def load_video(video_path):
41
+ """
42
+ Load a video file and return a VideoCapture object.
43
+
44
+ Args:
45
+ video_path (str): Path to the video file
46
+
47
+ Returns:
48
+ cv2.VideoCapture: OpenCV VideoCapture object
49
+
50
+ Raises:
51
+ FileNotFoundError: If the video file cannot be opened
52
+ """
53
+ video_capture = cv2.VideoCapture(video_path)
54
+ if not video_capture.isOpened():
55
+ raise FileNotFoundError(f"Cannot open video file: {video_path}")
56
+ return video_capture
57
+
58
+
59
+ def get_video_properties(video_capture):
60
+ """
61
+ Get properties of a video.
62
+
63
+ Args:
64
+ video_capture (cv2.VideoCapture): OpenCV VideoCapture object
65
+
66
+ Returns:
67
+ dict: Dictionary with video properties (fps, width, height, frame_count)
68
+ """
69
+ properties = {
70
+ 'fps': video_capture.get(cv2.CAP_PROP_FPS),
71
+ 'width': int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)),
72
+ 'height': int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)),
73
+ 'frame_count': int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
74
+ }
75
+ return properties
76
+
77
+
78
+ def extract_frames(video_capture, show_progress=True):
79
+ """
80
+ Extract all frames from a video.
81
+
82
+ Args:
83
+ video_capture (cv2.VideoCapture): OpenCV VideoCapture object
84
+ show_progress (bool): Whether to show a progress bar
85
+
86
+ Returns:
87
+ list: List of frames as numpy arrays
88
+ """
89
+ frames = []
90
+
91
+ # Get frame count for progress bar
92
+ frame_count = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
93
+
94
+ # Create progress bar if requested
95
+ if show_progress:
96
+ progress_bar = tqdm(total=frame_count, desc="Extracting frames")
97
+
98
+ while True:
99
+ ret, frame = video_capture.read()
100
+ if not ret:
101
+ break
102
+ frames.append(frame)
103
+
104
+ # Update progress bar
105
+ if show_progress:
106
+ progress_bar.update(1)
107
+
108
+ # Close progress bar
109
+ if show_progress:
110
+ progress_bar.close()
111
+
112
+ return frames
113
+
114
+
115
+ def save_video(output_path, frames, fps, show_progress=True):
116
+ """
117
+ Save a list of frames as a video file.
118
+
119
+ Args:
120
+ output_path (str): Path where the video will be saved
121
+ frames (list): List of frames as numpy arrays
122
+ fps (float): Frames per second for the output video
123
+ show_progress (bool): Whether to show a progress bar
124
+
125
+ Raises:
126
+ ValueError: If no frames are provided
127
+ """
128
+ if not frames:
129
+ raise ValueError("No frames to save.")
130
+
131
+ height, width, _ = frames[0].shape
132
+ codec = get_codec_for_file(output_path)
133
+ fourcc = cv2.VideoWriter_fourcc(*codec)
134
+ video_writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
135
+
136
+ # Create progress bar if requested
137
+ if show_progress:
138
+ progress_bar = tqdm(total=len(frames), desc="Saving video")
139
+
140
+ for frame in frames:
141
+ video_writer.write(frame)
142
+
143
+ # Update progress bar
144
+ if show_progress:
145
+ progress_bar.update(1)
146
+
147
+ # Close progress bar
148
+ if show_progress:
149
+ progress_bar.close()
150
+
151
+ video_writer.release()
152
+ print(f"Video saved to {output_path}")
153
+
154
+
155
+ def process_video_frames(video_path, output_path, process_function, **kwargs):
156
+ """
157
+ Process a video by applying a function to each frame.
158
+
159
+ Args:
160
+ video_path (str): Path to the input video
161
+ output_path (str): Path where processed video will be saved
162
+ process_function (callable): Function to apply to each frame
163
+ The function should take a frame and return a processed frame
164
+ **kwargs: Additional arguments to pass to the process_function
165
+
166
+ Example:
167
+ def grayscale(frame):
168
+ return cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
169
+
170
+ process_video_frames('input.mp4', 'output.mp4', grayscale)
171
+ """
172
+ # Load video
173
+ cap = load_video(video_path)
174
+
175
+ try:
176
+ # Get video properties
177
+ props = get_video_properties(cap)
178
+
179
+ # Create output video writer with appropriate codec
180
+ codec = get_codec_for_file(output_path)
181
+ fourcc = cv2.VideoWriter_fourcc(*codec)
182
+ out = cv2.VideoWriter(output_path, fourcc, props['fps'],
183
+ (props['width'], props['height']))
184
+
185
+ # Process frames with progress bar
186
+ progress_bar = tqdm(total=props['frame_count'], desc="Processing frames")
187
+
188
+ while True:
189
+ ret, frame = cap.read()
190
+ if not ret:
191
+ break
192
+
193
+ # Apply processing function
194
+ processed_frame = process_function(frame, **kwargs)
195
+
196
+ # Write processed frame
197
+ out.write(processed_frame)
198
+
199
+ # Update progress
200
+ progress_bar.update(1)
201
+
202
+ # Close progress bar
203
+ progress_bar.close()
204
+
205
+ print(f"Processed video saved to {output_path}")
206
+
207
+ finally:
208
+ # Release resources
209
+ cap.release()
210
+ if 'out' in locals():
211
+ out.release()
212
+
213
+
214
+ def release_resources(video_capture, video_writer=None):
215
+ """
216
+ Release video resources.
217
+
218
+ Args:
219
+ video_capture (cv2.VideoCapture): OpenCV VideoCapture object
220
+ video_writer (cv2.VideoWriter, optional): OpenCV VideoWriter object
221
+ """
222
+ if video_capture is not None:
223
+ video_capture.release()
224
+
225
+ if video_writer is not None:
226
+ video_writer.release()
@@ -0,0 +1,90 @@
1
+ """
2
+ Unit tests for the duotone module.
3
+ """
4
+ import unittest
5
+ import os
6
+ import tempfile
7
+ import numpy as np
8
+ import cv2
9
+ from src.duotone import apply_duotone
10
+
11
+
12
+ class TestDuotone(unittest.TestCase):
13
+ """Tests for the duotone effect functions."""
14
+
15
+ def setUp(self):
16
+ """Set up test environment."""
17
+ # Create a small test video file
18
+ self.temp_dir = tempfile.TemporaryDirectory()
19
+ self.input_path = os.path.join(self.temp_dir.name, "test_input.mp4")
20
+ self.output_path = os.path.join(self.temp_dir.name, "test_output.mp4")
21
+
22
+ # Create a simple test video (10 frames, solid color)
23
+ width, height = 320, 240
24
+ fps = 30
25
+
26
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
27
+ out = cv2.VideoWriter(self.input_path, fourcc, fps, (width, height))
28
+
29
+ # Create 10 frames
30
+ for i in range(10):
31
+ # Create gradient frame
32
+ frame = np.zeros((height, width, 3), dtype=np.uint8)
33
+ for y in range(height):
34
+ value = int(y * 255 / height)
35
+ frame[y, :] = [value, value, value]
36
+
37
+ out.write(frame)
38
+
39
+ out.release()
40
+
41
+ def tearDown(self):
42
+ """Clean up test environment."""
43
+ self.temp_dir.cleanup()
44
+
45
+ def test_apply_duotone_basic(self):
46
+ """Test basic duotone effect application."""
47
+ # Skip test if codec not available
48
+ try:
49
+ # Test with basic colors
50
+ color1 = (255, 0, 0) # Red
51
+ color2 = (0, 255, 255) # Cyan
52
+
53
+ apply_duotone(self.input_path, self.output_path, color1, color2)
54
+
55
+ # Verify the output exists
56
+ self.assertTrue(os.path.exists(self.output_path))
57
+
58
+ # Verify the output has correct content
59
+ cap = cv2.VideoCapture(self.output_path)
60
+ self.assertTrue(cap.isOpened())
61
+
62
+ # Read the first frame and check colors
63
+ ret, frame = cap.read()
64
+ self.assertTrue(ret)
65
+
66
+ # Check frame dimensions
67
+ self.assertEqual(frame.shape[1], 320) # Width
68
+ self.assertEqual(frame.shape[0], 240) # Height
69
+
70
+ # Close video
71
+ cap.release()
72
+ except cv2.error:
73
+ self.skipTest("Codec not available")
74
+
75
+ def test_invalid_inputs(self):
76
+ """Test error handling for invalid inputs."""
77
+ # Test with non-existent input file
78
+ with self.assertRaises(FileNotFoundError):
79
+ apply_duotone("nonexistent.mp4", self.output_path, (255, 0, 0), (0, 255, 255))
80
+
81
+ # Test with invalid colors
82
+ with self.assertRaises(ValueError):
83
+ apply_duotone(self.input_path, self.output_path, (300, 0, 0), (0, 255, 255))
84
+
85
+ with self.assertRaises(ValueError):
86
+ apply_duotone(self.input_path, self.output_path, (255, 0, 0), (-10, 255, 255))
87
+
88
+
89
+ if __name__ == "__main__":
90
+ unittest.main()
@@ -0,0 +1,119 @@
1
+ """
2
+ Unit tests for the halftone module.
3
+ """
4
+ import unittest
5
+ import os
6
+ import tempfile
7
+ import numpy as np
8
+ import cv2
9
+ from src.halftone import apply_halftone
10
+
11
+
12
+ class TestHalftone(unittest.TestCase):
13
+ """Tests for the halftone effect functions."""
14
+
15
+ def setUp(self):
16
+ """Set up test environment."""
17
+ # Create a small test video file
18
+ self.temp_dir = tempfile.TemporaryDirectory()
19
+ self.input_path = os.path.join(self.temp_dir.name, "test_input.mp4")
20
+ self.output_path = os.path.join(self.temp_dir.name, "test_output.mp4")
21
+
22
+ # Create a simple test video (10 frames, gradient color)
23
+ width, height = 320, 240
24
+ fps = 30
25
+
26
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
27
+ out = cv2.VideoWriter(self.input_path, fourcc, fps, (width, height))
28
+
29
+ # Create 10 frames
30
+ for i in range(10):
31
+ # Create gradient frame
32
+ frame = np.zeros((height, width, 3), dtype=np.uint8)
33
+ for y in range(height):
34
+ value = int(y * 255 / height)
35
+ frame[y, :] = [value, value, value]
36
+
37
+ out.write(frame)
38
+
39
+ out.release()
40
+
41
+ def tearDown(self):
42
+ """Clean up test environment."""
43
+ self.temp_dir.cleanup()
44
+
45
+ def test_apply_halftone_basic(self):
46
+ """Test basic halftone effect application."""
47
+ # Skip test if codec not available
48
+ try:
49
+ # Test with basic settings
50
+ color1 = (0, 0, 0) # Black
51
+ color2 = (255, 255, 255) # White
52
+ symbol_size = 8
53
+
54
+ apply_halftone(self.input_path, self.output_path, symbol_size, color1, color2)
55
+
56
+ # Verify the output exists
57
+ self.assertTrue(os.path.exists(self.output_path))
58
+
59
+ # Verify the output has correct content
60
+ cap = cv2.VideoCapture(self.output_path)
61
+ self.assertTrue(cap.isOpened())
62
+
63
+ # Read the first frame and check dimensions
64
+ ret, frame = cap.read()
65
+ self.assertTrue(ret)
66
+
67
+ # Check frame dimensions
68
+ self.assertEqual(frame.shape[1], 320) # Width
69
+ self.assertEqual(frame.shape[0], 240) # Height
70
+
71
+ # Close video
72
+ cap.release()
73
+ except cv2.error:
74
+ self.skipTest("Codec not available")
75
+
76
+ def test_symbol_types(self):
77
+ """Test different symbol types."""
78
+ # Skip test if codec not available
79
+ try:
80
+ symbol_types = ['plus', 'asterisk', 'slash']
81
+
82
+ for symbol_type in symbol_types:
83
+ output_path = os.path.join(self.temp_dir.name, f"test_{symbol_type}.mp4")
84
+
85
+ apply_halftone(
86
+ self.input_path,
87
+ output_path,
88
+ symbol_size=8,
89
+ color1_rgb=(0, 0, 0),
90
+ color2_rgb=(255, 255, 255),
91
+ symbol_type=symbol_type
92
+ )
93
+
94
+ # Verify the output exists
95
+ self.assertTrue(os.path.exists(output_path))
96
+ except cv2.error:
97
+ self.skipTest("Codec not available")
98
+
99
+ def test_invalid_inputs(self):
100
+ """Test error handling for invalid inputs."""
101
+ # Test with non-existent input file
102
+ with self.assertRaises(FileNotFoundError):
103
+ apply_halftone("nonexistent.mp4", self.output_path, 10, (0, 0, 0), (255, 255, 255))
104
+
105
+ # Test with invalid colors
106
+ with self.assertRaises(ValueError):
107
+ apply_halftone(self.input_path, self.output_path, 10, (300, 0, 0), (255, 255, 255))
108
+
109
+ # Test with invalid symbol type
110
+ with self.assertRaises(ValueError):
111
+ apply_halftone(self.input_path, self.output_path, 10, (0, 0, 0), (255, 255, 255), 'invalid_type')
112
+
113
+ # Test with invalid symbol size
114
+ with self.assertRaises(ValueError):
115
+ apply_halftone(self.input_path, self.output_path, 0, (0, 0, 0), (255, 255, 255))
116
+
117
+
118
+ if __name__ == "__main__":
119
+ unittest.main()