One useful idea
Frames divided by frames per second gives seconds. Keep the rate as a number with its fractional part: 29.97 is not 30. Converting it with int silently changes the calculation.
Store the unrounded result for calculations. Format only when displaying it. Our display is elapsed duration MM:SS.mmm, not SMPTE timecode. Minutes may exceed 59.
For formatting, round the total number of milliseconds first. divmod(total_ms, 60000) returns a pair: whole minutes and remaining milliseconds. Split the remainder again with 1000. This order lets 59.9996 seconds carry into the next minute rather than print 00:60.000.
Refresh first: Functions with validated inputs, Frame counts and division.
Trace a finished example
from asset_tools import seconds_from_frames, format_duration
seconds = seconds_from_frames(29970, 29.97)
print(seconds)
print(format_duration(seconds))
# 1000.0
# 16:40.00029970 / 29.97 is 1000 seconds. Formatting computes 1000000 milliseconds, then 16 minutes with 40000 milliseconds left, then 40 seconds with 0 milliseconds left.
The finished implementation is in asset_tools.py. Reading it is guided practice, not independent evidence.
Predict before running
What is seconds_from_frames(240, 24), and how should it display?
Compare your answer · self-reviewed
10.0 seconds, displayed as 00:10.000. A trailing .0 is a numeric display detail, not evidence of a wrong value.
Find the bug
Why does frames / int(fps) give the wrong result at 29.97 fps?
Compare your answer · self-reviewed
int(29.97) becomes 29. The denominator changes before division. Keep fps unchanged; format only the final display.
Predict the boundary
Should format_duration(59.9996) return 00:60.000 or 01:00.000?
Compare your answer · self-reviewed
01:00.000. Round the total milliseconds first, then split minutes, seconds and milliseconds so carry is handled consistently.
Try the idea in this browser
Runs in this browser · optional preparation · local project checks remain separate
Try a small function before opening your local files. Python downloads when you choose Run; if it cannot load, your code stays here and the local kit still works. The worker executes on your device, not on a DVP server. Only run code you trust: this is not a hostile-code security sandbox.
JavaScript loads the practice controls. Python starts only after Run.
The tutor button only prepares a question locally. Review it and choose Send yourself; no code is sent merely by running or opening a lesson.
Output
Errors and check feedback
Read the browser task briefs without running Python
Read a duration calculation
Predict the printed seconds, then Run. The function must also work at a fractional frame rate and for a single frame. Keep the result unrounded; this is calculation, not display formatting.
Validate frame counts and rates
Validate frames as an integer (not bool), from 0 through 1,000,000,000. Validate fps as int/float (not bool), finite, from 0.001 through 1000. Raise TypeError for wrong types and ValueError for refused numeric values. Keep fractional rates intact.
Format a duration with minute carry
Write format_duration(seconds). Return MM:SS.mmm with at least two minute digits; minutes may exceed 59. Round total milliseconds before splitting components. Accept finite int/float values from 0 through 1,000,000,000,000, excluding bool; raise TypeError for wrong types and ValueError for invalid numbers. This is elapsed duration, not SMPTE timecode.
Change it, then build your own
One controlled change
Run the reference for 24000 frames at 23.976 fps and 25000 at 25 fps. Compare the returned seconds before formatting; do not assume equal frame counts mean equal durations.
Your independent task
Implement seconds_from_frames and format_duration in practice.py. Frames must be an integer (not bool), from 0 to 1,000,000,000. Rates must be finite int/float values (not bool), from 0.001 to 1000. Seconds for display must be finite and from 0 to 1,000,000,000,000. Import math for math.isfinite; raise TypeError for the wrong type and ValueError for a refused numeric value.
What success looks like
Six duration test methods pass. Checks include fractional rates, zero frames, negative inputs, NaN/infinity and a minute boundary. Naming tests are separate; --build 2 isolates this build.
Hint 1 · a question
Write the units beside frames / fps. Where would rounding lose useful information?
Hint 2 · a concept cue
Validate type, finiteness and range before division. Use a single total_ms for display rather than rounding each component independently.
Hint 3 · a localized example
minutes, remainder = divmod(round(seconds * 1000), 60000); whole_seconds, milliseconds = divmod(remainder, 1000). Format each part with its own minimum width.
Need the complete worked solution?
Open asset_tools.py from the kit. Trace it, close it, then try fresh inputs in your own files. Treat the attempt as guided; seeing the solution does not award a practical pass.
Course help is guidance, not independent evidence. With JavaScript, opening help records guidance locally; otherwise note it in your README. Reset does not erase that history.
Repair a failed check
If a fractional-rate case fails, look for int(fps) or early rounding. If a minute boundary fails, round seconds * 1000 before divmod. If True passes, explicitly exclude bool before treating values as numbers.
NotImplementedError means a practice stub is still unfinished. Read the failing test name and the last error line. Change one behavior, rerun that build, then rerun all implemented builds.
Show it works on new inputs
Calculate 29970 frames at 29.97 fps without reading the worked answer. Add a case just below a minute boundary and an invalid rate. Explain why a rounded frame rate can create delivery timing errors.
Self-review: name the input, result, refused case and reason. Your local test output and explanation are separate from a quiz score; this page does not certify a pass.
Keep the idea
Calculation and presentation are different jobs. Preserve precision in returned data; round once when constructing the human-readable display.