@@ -187,6 +187,20 @@ def _extract_iphone_model(exif_out: str) -> str | None:
187187
188188 @staticmethod
189189 def _is_edited_fps (file_path : Path ) -> bool :
190+ """Heuristic: re-encoded/edited video tends to have an integer FPS fraction.
191+
192+ Native camera footage uses fractional rates:
193+ - 30000/1001 ≈ 29.97 fps (standard NTSC)
194+ - 60000/1001 ≈ 59.94 fps
195+ Re-encoded/edited footage uses exact integer fractions:
196+ - 30/1 = exactly 30 fps
197+ - 60/1 = exactly 60 fps
198+
199+ Comparing the rounded float (as the original code did) causes false positives
200+ because 30000/1001 rounds to 29.97, not 30.0 — but some recording modes and
201+ screen-recording tools do produce genuine 30/1 or 60/1 streams on unedited
202+ files. Using the denominator is a stronger discriminator.
203+ """
190204 try :
191205 result = subprocess .run (
192206 [
@@ -207,11 +221,12 @@ def _is_edited_fps(file_path: Path) -> bool:
207221 )
208222 fraction = result .stdout .strip ()
209223 if "/" in fraction :
210- num , den = map (float , fraction .split ("/" ))
211- if den > 0 :
212- exact_fps = round (num / den , 6 )
213- if exact_fps in (30.000000 , 60.000000 ):
214- return True
224+ parts = fraction .split ("/" , 1 )
225+ num , den = float (parts [0 ]), float (parts [1 ])
226+ # Native camera: denominator is typically 1001 (e.g. 30000/1001 = 29.97)
227+ # Re-encoded: denominator is 1 (e.g. 30/1, 60/1)
228+ if den > 0 and int (den ) == 1 and int (num ) in (30 , 60 ):
229+ return True
215230 return False
216231 except (FileNotFoundError , ValueError ):
217232 return False
0 commit comments