merleau 0.1.1__py3-none-any.whl → 0.3.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.
merleau/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Merleau - Video analysis using Google's Gemini API."""
2
+
3
+ __version__ = "0.3.0"
merleau/cli.py ADDED
@@ -0,0 +1,215 @@
1
+ """Command-line interface for Merleau video analysis."""
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+ import time
7
+ from dataclasses import dataclass
8
+ from typing import Callable, Optional
9
+
10
+ from dotenv import load_dotenv
11
+ from google import genai
12
+
13
+
14
+ @dataclass
15
+ class AnalysisResult:
16
+ """Result from video analysis."""
17
+ text: str
18
+ prompt_tokens: int
19
+ response_tokens: int
20
+ total_tokens: int
21
+ input_cost: float
22
+ output_cost: float
23
+ total_cost: float
24
+
25
+
26
+ def wait_for_processing(client, file, on_progress: Optional[Callable] = None):
27
+ """Wait for file to finish processing."""
28
+ while file.state.name == "PROCESSING":
29
+ if on_progress:
30
+ on_progress()
31
+ else:
32
+ print(".", end="", flush=True)
33
+ time.sleep(2)
34
+ file = client.files.get(name=file.name)
35
+ if not on_progress:
36
+ print()
37
+ return file
38
+
39
+
40
+ def calculate_cost(usage):
41
+ """Calculate cost from usage metadata."""
42
+ # Gemini 2.5 Flash pricing (as of 2025):
43
+ # Input: $0.15 per 1M tokens (text/image), $0.075 per 1M tokens for video
44
+ # Output: $0.60 per 1M tokens, $3.50 for thinking tokens
45
+ input_cost = (usage.prompt_token_count / 1_000_000) * 0.15
46
+ output_cost = (usage.candidates_token_count / 1_000_000) * 0.60
47
+ return input_cost, output_cost, input_cost + output_cost
48
+
49
+
50
+ def print_usage(usage):
51
+ """Print token usage and cost estimation."""
52
+ print("\n--- Usage Information ---")
53
+ print(f"Prompt tokens: {usage.prompt_token_count}")
54
+ print(f"Response tokens: {usage.candidates_token_count}")
55
+ print(f"Total tokens: {usage.total_token_count}")
56
+
57
+ input_cost, output_cost, total_cost = calculate_cost(usage)
58
+ print(f"\nEstimated cost:")
59
+ print(f" Input: ${input_cost:.6f}")
60
+ print(f" Output: ${output_cost:.6f}")
61
+ print(f" Total: ${total_cost:.6f}")
62
+
63
+
64
+ def analyze_video(
65
+ video_path: str,
66
+ prompt: str = "Explain what happens in this video",
67
+ model: str = "gemini-2.5-flash",
68
+ api_key: Optional[str] = None,
69
+ on_upload: Optional[Callable[[str], None]] = None,
70
+ on_processing: Optional[Callable] = None,
71
+ on_analyzing: Optional[Callable] = None,
72
+ ) -> AnalysisResult:
73
+ """
74
+ Analyze a video file using Gemini.
75
+
76
+ Args:
77
+ video_path: Path to the video file
78
+ prompt: Analysis prompt
79
+ model: Gemini model to use
80
+ api_key: Optional API key (falls back to env var)
81
+ on_upload: Callback when upload completes (receives file URI)
82
+ on_processing: Callback during processing (called repeatedly)
83
+ on_analyzing: Callback when analysis starts
84
+
85
+ Returns:
86
+ AnalysisResult with text, tokens, and cost
87
+
88
+ Raises:
89
+ ValueError: If API key not found or file doesn't exist
90
+ RuntimeError: If file processing fails
91
+ """
92
+ load_dotenv()
93
+
94
+ api_key = api_key or os.getenv("GEMINI_API_KEY")
95
+ if not api_key:
96
+ raise ValueError("GEMINI_API_KEY not found in environment or .env file")
97
+
98
+ if not os.path.exists(video_path):
99
+ raise ValueError(f"Video file not found: {video_path}")
100
+
101
+ client = genai.Client(api_key=api_key)
102
+
103
+ # Upload video
104
+ myfile = client.files.upload(file=video_path)
105
+ if on_upload:
106
+ on_upload(myfile.uri)
107
+
108
+ # Wait for processing
109
+ myfile = wait_for_processing(client, myfile, on_progress=on_processing)
110
+
111
+ if myfile.state.name == "FAILED":
112
+ raise RuntimeError("File processing failed")
113
+
114
+ # Generate analysis
115
+ if on_analyzing:
116
+ on_analyzing()
117
+
118
+ response = client.models.generate_content(
119
+ model=model,
120
+ contents=[myfile, prompt]
121
+ )
122
+
123
+ # Extract usage info
124
+ usage = response.usage_metadata
125
+ input_cost, output_cost, total_cost = calculate_cost(usage)
126
+
127
+ return AnalysisResult(
128
+ text=response.text,
129
+ prompt_tokens=usage.prompt_token_count,
130
+ response_tokens=usage.candidates_token_count,
131
+ total_tokens=usage.total_token_count,
132
+ input_cost=input_cost,
133
+ output_cost=output_cost,
134
+ total_cost=total_cost,
135
+ )
136
+
137
+
138
+ def analyze(video_path, prompt, model, show_cost):
139
+ """Analyze a video file using Gemini (CLI wrapper)."""
140
+ try:
141
+ print(f"Uploading video: {video_path}")
142
+
143
+ def on_upload(uri):
144
+ print(f"Upload complete. File URI: {uri}")
145
+ print("Waiting for file to be processed...", end="")
146
+
147
+ def on_processing():
148
+ print(".", end="", flush=True)
149
+
150
+ def on_analyzing():
151
+ print()
152
+ print(f"\nAnalyzing video with {model}...")
153
+
154
+ result = analyze_video(
155
+ video_path=video_path,
156
+ prompt=prompt,
157
+ model=model,
158
+ on_upload=on_upload,
159
+ on_processing=on_processing,
160
+ on_analyzing=on_analyzing,
161
+ )
162
+
163
+ print("\n--- Video Analysis ---")
164
+ print(result.text)
165
+
166
+ if show_cost:
167
+ print("\n--- Usage Information ---")
168
+ print(f"Prompt tokens: {result.prompt_tokens}")
169
+ print(f"Response tokens: {result.response_tokens}")
170
+ print(f"Total tokens: {result.total_tokens}")
171
+ print(f"\nEstimated cost:")
172
+ print(f" Input: ${result.input_cost:.6f}")
173
+ print(f" Output: ${result.output_cost:.6f}")
174
+ print(f" Total: ${result.total_cost:.6f}")
175
+
176
+ except ValueError as e:
177
+ print(f"Error: {e}", file=sys.stderr)
178
+ sys.exit(1)
179
+ except RuntimeError as e:
180
+ print(f"Error: {e}", file=sys.stderr)
181
+ sys.exit(1)
182
+
183
+
184
+ def main():
185
+ """Main entry point for the CLI."""
186
+ parser = argparse.ArgumentParser(
187
+ prog="ponty",
188
+ description="Analyze videos using Google's Gemini API"
189
+ )
190
+ parser.add_argument(
191
+ "video",
192
+ help="Path to the video file to analyze"
193
+ )
194
+ parser.add_argument(
195
+ "-p", "--prompt",
196
+ default="Explain what happens in this video",
197
+ help="Prompt for the analysis (default: 'Explain what happens in this video')"
198
+ )
199
+ parser.add_argument(
200
+ "-m", "--model",
201
+ default="gemini-2.5-flash",
202
+ help="Gemini model to use (default: gemini-2.5-flash)"
203
+ )
204
+ parser.add_argument(
205
+ "--no-cost",
206
+ action="store_true",
207
+ help="Hide usage and cost information"
208
+ )
209
+
210
+ args = parser.parse_args()
211
+ analyze(args.video, args.prompt, args.model, show_cost=not args.no_cost)
212
+
213
+
214
+ if __name__ == "__main__":
215
+ main()
@@ -1,7 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: merleau
3
- Version: 0.1.1
3
+ Version: 0.3.0
4
4
  Summary: Video analysis using Google's Gemini 2.5 Flash API
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: google-genai
7
7
  Requires-Dist: python-dotenv
8
+ Provides-Extra: web
9
+ Requires-Dist: streamlit>=1.30.0; extra == 'web'
@@ -0,0 +1,6 @@
1
+ merleau/__init__.py,sha256=zV-fMC9hX4Uq5Kb4Jegk84CouKUslY58WkdBNCMMVck,81
2
+ merleau/cli.py,sha256=1NofguSaSdB1RQpSb9Po6BudhQp6Q-3vYiGNky6vbmo,6396
3
+ merleau-0.3.0.dist-info/METADATA,sha256=XKLO1o4-g-sK2xFm38CB33NRzpQk5c7mc5sSfi6Ry8g,261
4
+ merleau-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
5
+ merleau-0.3.0.dist-info/entry_points.txt,sha256=pMCcADIqyZbK-lQ5d2iLKIWqNeeY3pAQDHI1IBmtdEQ,43
6
+ merleau-0.3.0.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.2)
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ponty = merleau.cli:main
@@ -1,4 +0,0 @@
1
- merleau-0.1.1.dist-info/METADATA,sha256=fXz21-WXC04ec7uVE7V6f4uUkX_udosyBOC5qmXbUBQ,192
2
- merleau-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
3
- merleau-0.1.1.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
4
- merleau-0.1.1.dist-info/RECORD,,
@@ -1 +0,0 @@
1
-