FirstFrame 1.0.1__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.
LastFrame/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import ChronicleLogger
2
+
3
+ __version__ = "1.0.1"
4
+ __all__ = ["ChronicleLogger"]
LastFrame/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
LastFrame/cli.py ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python
2
+ from __future__ import print_function, unicode_literals
3
+
4
+ from ChronicleLogger import ChronicleLogger
5
+ import sys
6
+ import cv2
7
+ import os
8
+ import glob
9
+
10
+ def get_last_frame(video_path, output_path):
11
+ """Extract the last frame from video and save it."""
12
+ cap = cv2.VideoCapture(video_path)
13
+ if not cap.isOpened():
14
+ print("Error: Cannot open video file.")
15
+ return False
16
+
17
+ # Method 1: Try by frame count (most accurate)
18
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
19
+ if total_frames > 1:
20
+ cap.set(cv2.CAP_PROP_POS_FRAMES, total_frames - 1)
21
+ ret, frame = cap.read()
22
+ if ret:
23
+ cv2.imwrite(output_path, frame)
24
+ cap.release()
25
+ print(f"Success: Last frame saved as '{output_path}'")
26
+ return True
27
+
28
+ # Method 2: Fallback - seek to 99% of video
29
+ cap.set(cv2.CAP_PROP_POS_AVI_RATIO, 0.99)
30
+ ret, frame = cap.read()
31
+ if ret:
32
+ cv2.imwrite(output_path, frame)
33
+ cap.release()
34
+ print(f"Success: Last frame saved as '{output_path}' (using time-based seek)")
35
+ return True
36
+
37
+ # Method 3: Use millisecond seek
38
+ fps = cap.get(cv2.CAP_PROP_FPS)
39
+ if fps > 0:
40
+ duration_ms = (total_frames / fps) * 1000
41
+ cap.set(cv2.CAP_PROP_POS_MSEC, duration_ms - 100) # 0.1s before end
42
+ ret, frame = cap.read()
43
+ if ret:
44
+ cv2.imwrite(output_path, frame)
45
+ cap.release()
46
+ print(f"Success: Last frame saved as '{output_path}' (millisecond seek)")
47
+ return True
48
+
49
+ cap.release()
50
+ print("Failed: Could not extract the last frame.")
51
+ return False
52
+
53
+
54
+ def main():
55
+ # Find all .mp4 files in current directory (case-insensitive)
56
+ mp4_files = glob.glob("*.mp4") + glob.glob("*.MP4") + glob.glob("*.Mp4")
57
+ mp4_files = sorted(set(mp4_files)) # Remove duplicates, sort nicely
58
+
59
+ if not mp4_files:
60
+ print("No .mp4 videos found in the current folder!")
61
+ print(f"Current directory: {os.getcwd()}")
62
+ input("\nPress Enter to exit...")
63
+ return
64
+
65
+ # Display numbered list
66
+ print("\nFound MP4 videos:\n")
67
+ for i, filename in enumerate(mp4_files, 1):
68
+ size_mb = os.path.getsize(filename) / (1024 * 1024)
69
+ print(f" {i:2d}. {filename} ({size_mb:.1f} MB)")
70
+
71
+ # Get user choice
72
+ while True:
73
+ try:
74
+ choice = input(f"\nEnter video number (1-{len(mp4_files)}): ").strip()
75
+ if not choice:
76
+ print("Please enter a number.")
77
+ continue
78
+ idx = int(choice) - 1
79
+ if 0 <= idx < len(mp4_files):
80
+ selected_video = mp4_files[idx]
81
+ break
82
+ else:
83
+ print(f"Please enter a number between 1 and {len(mp4_files)}.")
84
+ except ValueError:
85
+ print("Invalid input. Please type a number.")
86
+
87
+ # Suggest default output: either output.png or video_name_last.png
88
+ base_name = os.path.splitext(selected_video)[0]
89
+ default_output = "output.png" if os.path.exists("output.png") == False else f"{base_name}_last.png"
90
+
91
+ print(f"\nSelected: {selected_video}")
92
+
93
+ # Ask for output filename
94
+ user_output = input(f"Enter output image filename [default: {default_output}]: ").strip()
95
+ if not user_output:
96
+ output_path = default_output
97
+ else:
98
+ # Ensure it has an image extension
99
+ if not user_output.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
100
+ output_path = user_output + ".png"
101
+ else:
102
+ output_path = user_output
103
+
104
+ print(f"\nExtracting last frame from '{selected_video}'...")
105
+ get_last_frame(selected_video, output_path)
106
+
107
+ print(f"\nDone! Image saved as: {output_path}")
108
+ input("\nPress Enter to exit...")
109
+
110
+
111
+ if __name__ == "__main__":
112
+ main()
113
+
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: FirstFrame
3
+ Version: 1.0.1
4
+ Summary: FirstFrame is a simple command-line tool for extracting the last frame from MP4 video files using OpenCV (cv2)
5
+ Author-email: Wilgat Wong <wilgat.wong@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Wilgat/FirstFrame
8
+ Project-URL: Repository, https://github.com/Wilgat/FirstFrame
9
+ Project-URL: Issues, https://github.com/Wilgat/FirstFrame/issues
10
+ Keywords: logging,sudo,chronicle,example
11
+ Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: ChronicleLogger>=1.2.3
14
+ Requires-Dist: opencv-python
15
+
16
+ # FirstFrame
17
+
18
+ ## Overview
19
+
20
+ **FirstFrame** is a lightweight command-line tool that extracts the **first frame** from MP4 video files using OpenCV (`cv2`).
21
+
22
+ It automatically scans the current directory for `.mp4` files (case-insensitive), displays an interactive numbered list with file sizes, lets the user select a video, and saves the first frame as an image file (PNG by default; supports JPG, JPEG, BMP, TIFF).
23
+
24
+ The tool features clear prompts, basic error handling, and a pause at the end — perfect for quick thumbnail generation, cover image creation, or inspecting video starts without loading the entire file.
25
+
26
+ ## Features
27
+
28
+ - Finds all `.mp4` / `.MP4` / `.Mp4` files in the current folder using `glob`
29
+ - Shows numbered list with file sizes (in MB) for easy selection
30
+ - Extracts the very first frame reliably with a single `cap.read()` call
31
+ - Smart default output filename: `output.png` (if free) or `{video_name}_first.png`
32
+ - Automatically adds `.png` extension if user input lacks an image suffix
33
+ - Clean resource handling (`cap.release()`) — no temporary files
34
+ - User-friendly success/error messages and final confirmation
35
+
36
+ ## Prerequisites
37
+
38
+ - Python 3.8+ (recommended: 3.10–3.12)
39
+ - `opencv-python` (installed via pip)
40
+ - Optional: virtual environment (`venv` or `uv`)
41
+
42
+ No C compiler or Cython is required for normal usage — the project is currently pure Python.
43
+
44
+ ## Installation
45
+
46
+ ### Development / Source Install (Editable)
47
+
48
+ ```bash
49
+ # 1. Clone or download the repo
50
+ git clone <your-repo-url>
51
+ cd FirstFrame
52
+
53
+ # 2. (Recommended) Create & activate virtual environment
54
+ python3 -m venv venv
55
+ source venv/bin/activate # Windows: venv\Scripts\activate
56
+
57
+ # 3. Install dependencies
58
+ pip install opencv-python
59
+ # If you later add ChronicleLogger or others → add them here
60
+
61
+ # 4. Install project in editable mode
62
+ pip install -e .
63
+ ```
64
+
65
+ Now you can run it with:
66
+
67
+ ```bash
68
+ python -m FirstFrame
69
+ ```
70
+
71
+ ### Future PyPI Release (when ready)
72
+
73
+ ```bash
74
+ pip install firstframe
75
+ ```
76
+
77
+ (You would then publish it via `python -m build` + twine, and configure `console_scripts` in `pyproject.toml` for a `firstframe` command.)
78
+
79
+ ## Usage
80
+
81
+ Place your MP4 videos in the current directory and run:
82
+
83
+ ```bash
84
+ python -m FirstFrame
85
+ ```
86
+
87
+ Example session:
88
+
89
+ ```
90
+ Found MP4 videos:
91
+
92
+ 1. intro.mp4 (12.4 MB)
93
+ 2. demo.MP4 (45.8 MB)
94
+
95
+ Enter video number (1-2): 1
96
+
97
+ Selected: intro.mp4
98
+ Enter output image filename [default: output.png]:
99
+
100
+ Extracting first frame from 'intro.mp4'...
101
+ Success: First frame saved as 'output.png'
102
+
103
+ Done! Image saved as: output.png
104
+
105
+ Press Enter to exit...
106
+ ```
107
+
108
+ Or specify a custom name:
109
+
110
+ ```
111
+ Enter output image filename [default: intro_first.png]: cover.jpg
112
+ ```
113
+
114
+ The tool saves the image and waits for you to press Enter before closing.
115
+
116
+ For scripting / automation:
117
+
118
+ ```python
119
+ from FirstFrame.__main__ import main
120
+ main()
121
+ ```
122
+
123
+ ## Project Structure
124
+
125
+ Follows modern Python packaging conventions with `src/` layout:
126
+
127
+ ```
128
+ FirstFrame/
129
+ ├── README.md
130
+ ├── pyproject.toml # (add when packaging properly)
131
+ ├── src/
132
+ │ └── FirstFrame/
133
+ │ ├── __init__.py
134
+ │ └── __main__.py # Main logic + entry point
135
+ └── (optional future folders)
136
+ ├── tests/
137
+ └── docs/
138
+ ```
139
+
140
+ ## Development Notes
141
+
142
+ - The core logic lives in `src/FirstFrame/__main__.py`
143
+ - To add features (batch mode, other formats, progress bar…): edit the `main()` and `get_first_frame()` functions
144
+ - Logging: `ChronicleLogger` is imported but unused — either integrate it or remove the import
145
+ - Tests: Consider adding `tests/` folder + `pytest` later
146
+ - Packaging: When ready, add `pyproject.toml` with `build-system = {requires = ["hatchling"], build-backend = "hatchling.build"}` (or setuptools)
147
+
148
+ ## Troubleshooting
149
+
150
+ - **Cannot open video** → Check file path, integrity, and codec support in your OpenCV build
151
+ - **No MP4 files found** → Make sure videos are in the current working directory (`pwd`)
152
+ - **OpenCV not found** → `pip install opencv-python`
153
+ - **Permission denied on save** → Check write permissions in current folder
154
+
155
+ ## License
156
+
157
+ MIT License
158
+ (Add a `LICENSE` file with the standard MIT text when you publish or share the repo.)
@@ -0,0 +1,8 @@
1
+ LastFrame/__init__.py,sha256=bgv2y1LnxxM75lyCZQOD8n840ZfMIBUS4boSzy0zk9s,85
2
+ LastFrame/__main__.py,sha256=0g3iknXOS9gZUcpL_trgAcuCJnZZKjdsT_xt61WOVb4,60
3
+ LastFrame/cli.py,sha256=RIPcM2E05uQcIi6TyJ9XNr8MDounI-FVlp9Sy_JJtPU,3810
4
+ firstframe-1.0.1.dist-info/METADATA,sha256=ATNGZjKVOO5vIHMaiZbYvsKW1M9E24FMGxRuQrMieRg,4826
5
+ firstframe-1.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ firstframe-1.0.1.dist-info/entry_points.txt,sha256=mSDW-CTqz9esvqLH97SyNmbkio0YQ6y6IO76Vtbgq3w,52
7
+ firstframe-1.0.1.dist-info/top_level.txt,sha256=ViqkFFJ2aTDbEaUya4XACENmuH1tCf5Sm7Bun6oKclM,10
8
+ firstframe-1.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ first-frame = FirstFrame.cli:main
@@ -0,0 +1 @@
1
+ LastFrame