initial commit

This commit is contained in:
2025-08-19 00:09:13 -07:00
commit 490a182f72
5 changed files with 1032 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.git
data
.mypy_cache

286
README.md Normal file
View File

@@ -0,0 +1,286 @@
# MPV Immersion Tracker for Language Learning
A Lua script for MPV that helps you track your language learning immersion sessions. The script uses a manual keybinding to start/stop tracking, giving you full control over when to log your sessions.
## Features
- **Manual Session Control**: Press `Ctrl+L` to start/stop immersion tracking
- **Session Tracking**: Records start/end times, duration, and progress
- **Comprehensive Logging**: Tracks video metadata, formats, resolution, and more
- **Resume Support**: Continues tracking if you stop and resume watching later
- **CSV Export**: Saves all session data to a CSV file for analysis
- **Real-time Progress**: Monitors watch progress and saves periodically
- **MPV Integration**: Uses only MPV's built-in functions - no external dependencies
- **MPV Options**: Configure everything through MPV's standard configuration system
## Installation
1. **Locate your MPV scripts directory**:
- **Linux/macOS**: `~/.config/mpv/scripts/`
- **Windows**: `%APPDATA%\mpv\scripts\`
2. **Copy the script file**:
```bash
cp immersion-tracker.lua ~/.config/mpv/scripts/
```
3. **Restart MPV** or reload scripts with `Ctrl+Shift+r`
## Configuration
The script uses MPV's built-in options system. Add configuration to your `~/.config/mpv/mpv.conf` file:
### Basic Configuration
```ini
# Change the keybinding (default: ctrl+l)
immersion-tracker-start_tracking_key=ctrl+k
# Change save frequency (default: 10 seconds)
immersion-tracker-save_interval=30
# Customize session naming
immersion-tracker-custom_prefix=[My Study]
immersion-tracker-max_title_length=80
```
### Advanced Configuration
```ini
# Enable debug logging
immersion-tracker-enable_debug_logging=true
# Show progress milestones
immersion-tracker-show_progress_milestones=true
immersion-tracker-milestone_percentages=10,25,50,75,90
# Customize notifications
immersion-tracker-show_session_start=false
immersion-tracker-show_session_end=true
# Export settings
immersion-tracker-export_csv=true
immersion-tracker-backup_csv=true
```
### Complete Configuration Reference
See `mpv.conf.example` for all available options and example configurations.
## Usage
### Basic Usage
1. **Start MPV** with any video file
2. **Press `Ctrl+L`** to start immersion tracking
3. **Watch normally** - the script tracks everything in the background
4. **Press `Ctrl+L` again** to stop tracking
5. **Check the console** for tracking messages (press `~` to toggle console)
### Keybindings
- **`Ctrl+L`**: Start/stop immersion tracking (configurable)
- **`~`**: Toggle console to see tracking messages
### Manual Control
- **Start tracking**: Press `Ctrl+L` anytime during playback
- **Stop tracking**: Press `Ctrl+L` again to end the session
- **Check status**: Look for `[Immersion Tracker]` messages in the console
- **View data**: Check the generated CSV file in the `data/` directory
## Data Output
### CSV Format
The script generates a CSV file with the following columns:
| Column | Description |
| ------------ | ---------------------------------- |
| Session ID | Unique identifier for each session |
| Title | Video title or filename |
| Filename | Original filename |
| Path | Full file path |
| Duration | Total video duration in seconds |
| Start Time | Session start timestamp |
| End Time | Session end timestamp |
| Watch Time | Total time spent watching |
| Progress | Percentage of video watched |
| Video Format | Video codec |
| Audio Format | Audio codec |
| Resolution | Video resolution |
| FPS | Frame rate |
| Bitrate | Video bitrate |
### Session Files
- **Current session**: `data/current_session.json` (for resuming)
- **Backup files**: Previous sessions are backed up automatically
## Session Management
### Starting a Session
- Press `Ctrl+L` to start tracking
- The script will gather current video information
- Session data is saved immediately
- On-screen message confirms tracking has started (if enabled)
### Stopping a Session
- Press `Ctrl+L` again to stop tracking
- End time and total watch time are calculated
- Data is saved to CSV
- On-screen message shows completion percentage (if enabled)
### Resume Support
- If you stop watching and restart later, the script can resume tracking
- Progress is maintained across sessions
- Previous session data is preserved
## Advanced Features
### Auto-save
- Session data is saved every 10 seconds (configurable)
- Ensures data isn't lost if MPV crashes
- Graceful shutdown handling
### Progress Tracking
- Real-time watch progress monitoring
- Seek detection and position tracking
- Duration and completion percentage
- Configurable progress milestones
### On-screen Messages
- Configurable confirmation when tracking starts/stops
- Progress information displayed
- Milestone notifications (optional)
### Session Naming
- Use media title or filename
- Custom prefixes
- Length limits
- Automatic truncation
## Configuration Options
### Keybinding Settings
- `start_tracking_key`: Key to start/stop tracking (default: `ctrl+l`)
### File Paths
- `data_dir`: Data directory (default: `data`)
- `csv_file`: CSV output file (default: `data/immersion_log.csv`)
- `session_file`: Session file (default: `data/current_session.json`)
### Tracking Settings
- `save_interval`: Auto-save frequency in seconds (default: `10`)
- `min_session_duration`: Minimum session duration (default: `30`)
### Session Naming
- `use_title`: Use media title (default: `true`)
- `use_filename`: Use filename instead (default: `false`)
- `custom_prefix`: Custom prefix (default: `[Immersion] `)
- `max_title_length`: Maximum title length (default: `100`)
### Notifications
- `show_session_start`: Show start message (default: `true`)
- `show_session_end`: Show end message (default: `true`)
- `show_progress_milestones`: Show milestones (default: `false`)
- `milestone_percentages`: Milestone percentages (default: `25,50,75,90`)
### Export Settings
- `export_csv`: Export to CSV (default: `true`)
- `backup_csv`: Create backups (default: `true`)
## Troubleshooting
### Common Issues
1. **Script not loading**:
- Check MPV scripts directory path
- Verify file permissions
- Check console for error messages
2. **No tracking data**:
- Ensure you've pressed `Ctrl+L` to start tracking
- Check console for tracking messages
- Verify data directory exists
3. **Permission errors**:
- Ensure write access to scripts directory
- Check data directory permissions
4. **Configuration not working**:
- Verify options are in `~/.config/mpv/mpv.conf`
- Check option names (use `immersion-tracker-` prefix)
- Restart MPV or reload scripts
### Debug Mode
Enable debug logging in your `mpv.conf`:
```ini
immersion-tracker-enable_debug_logging=true
```
### Console Messages
Look for these messages in the MPV console:
- `[Immersion Tracker] Script initialized`
- `[Immersion Tracker] Configuration loaded:`
- `[Immersion Tracker] Press ctrl+l to start/stop immersion tracking`
- `[Immersion Tracker] New immersion session started`
- `[Immersion Tracker] Session ended`
## Data Analysis
### CSV Analysis Tools
- **Spreadsheets**: Open in Excel, Google Sheets, or LibreOffice
- **Python**: Use pandas for data analysis
- **R**: Import and analyze with RStudio
- **Command line**: Use `awk`, `sed`, or `csvkit`
### Example Queries
```bash
# Total watch time
awk -F',' 'NR>1 {sum+=$8} END {print "Total watch time:", sum, "seconds"}'
# Most watched content
awk -F',' 'NR>1 {print $2}' | sort | uniq -c | sort -nr
# Daily progress
awk -F',' 'NR>1 {print $6}' | cut -d' ' -f1 | sort | uniq -c
```
## Future Enhancements
- **Database support**: MySQL/PostgreSQL integration
- **Web dashboard**: View statistics in a browser
- **Export formats**: JSON, XML, or custom formats
- **Advanced analytics**: Watch patterns, learning goals
- **Cloud sync**: Backup data to cloud storage
## Contributing
Feel free to submit issues, feature requests, or pull requests to improve the script.
## License
This script is provided as-is for educational and personal use.
## Support
For issues or questions:
1. Check the console for error messages
2. Verify your MPV configuration
3. Check file permissions and paths
4. Review the troubleshooting section above
5. Check `mpv.conf.example` for configuration examples

298
analyze_data.py Normal file
View File

@@ -0,0 +1,298 @@
#!/usr/bin/env python3
"""
MPV Immersion Tracker Data Analyzer
Analyzes the CSV data generated by the immersion tracker script
"""
import argparse
import csv
import os
import sys
from collections import Counter, defaultdict
from datetime import datetime, timedelta
class ImmersionAnalyzer:
def __init__(self, csv_file):
self.csv_file = csv_file
self.data = []
self.load_data()
def load_data(self):
"""Load data from CSV file"""
if not os.path.exists(self.csv_file):
print(f"Error: CSV file '{self.csv_file}' not found!")
sys.exit(1)
try:
with open(self.csv_file, "r", encoding="utf-8") as file:
reader = csv.DictReader(file)
for row in reader:
# Convert numeric fields
try:
row["Duration"] = int(row["Duration"])
row["Watch Time"] = int(row["Watch Time"])
row["Progress"] = float(row["Progress"])
except (ValueError, KeyError):
continue
# Parse timestamps
try:
row["Start Time"] = datetime.strptime(
row["Start Time"], "%Y-%m-%d %H:%M:%S"
)
row["End Time"] = datetime.strptime(
row["End Time"], "%Y-%m-%d %H:%M:%S"
)
except (ValueError, KeyError):
continue
self.data.append(row)
print(f"Loaded {len(self.data)} sessions from {self.csv_file}")
except Exception as e:
print(f"Error loading CSV file: {e}")
sys.exit(1)
def total_watch_time(self):
"""Calculate total watch time"""
total_seconds = sum(row["Watch Time"] for row in self.data)
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
return hours, minutes, total_seconds
def total_content_duration(self):
"""Calculate total content duration"""
total_seconds = sum(row["Duration"] for row in self.data)
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
return hours, minutes, total_seconds
def average_progress(self):
"""Calculate average completion percentage"""
if not self.data:
return 0
return sum(row["Progress"] for row in self.data) / len(self.data)
def most_watched_content(self, limit=10):
"""Find most watched content"""
content_watch_time = defaultdict(int)
for row in self.data:
content_watch_time[row["Title"]] += row["Watch Time"]
return sorted(content_watch_time.items(), key=lambda x: x[1], reverse=True)[
:limit
]
def daily_progress(self):
"""Calculate daily watch time"""
daily_watch = defaultdict(int)
for row in self.data:
date = row["Start Time"].date()
daily_watch[date] += row["Watch Time"]
return sorted(daily_watch.items())
def weekly_progress(self):
"""Calculate weekly watch time"""
weekly_watch = defaultdict(int)
for row in self.data:
# Get the week start (Monday)
date = row["Start Time"].date()
week_start = date - timedelta(days=date.weekday())
weekly_watch[week_start] += row["Watch Time"]
return sorted(weekly_watch.items())
def monthly_progress(self):
"""Calculate monthly watch time"""
monthly_watch = defaultdict(int)
for row in self.data:
month_start = row["Start Time"].replace(day=1).date()
monthly_watch[month_start] += row["Watch Time"]
return sorted(monthly_watch.items())
def format_duration(self, seconds):
"""Format duration in human-readable format"""
hours = seconds // 3600
minutes = (seconds % 3600) // 60
if hours > 0:
return f"{hours}h {minutes}m"
else:
return f"{minutes}m"
def print_summary(self):
"""Print a comprehensive summary"""
print("\n" + "=" * 60)
print("MPV IMMERSION TRACKER - DATA SUMMARY")
print("=" * 60)
if not self.data:
print("No data found!")
return
# Basic stats
total_hours, total_minutes, total_seconds = self.total_watch_time()
content_hours, content_minutes, content_seconds = self.total_content_duration()
avg_progress = self.average_progress()
print(f"\n📊 BASIC STATISTICS:")
print(f" Total sessions: {len(self.data)}")
print(
f" Total watch time: {total_hours}h {total_minutes}m ({total_seconds:,} seconds)"
)
print(
f" Total content duration: {content_hours}h {content_minutes}m ({content_seconds:,} seconds)"
)
print(f" Average completion: {avg_progress:.1f}%")
# Time analysis
print(f"\n⏰ TIME ANALYSIS:")
daily_data = self.daily_progress()
if daily_data:
total_days = len(daily_data)
avg_daily = total_seconds / total_days
avg_daily_h, avg_daily_m = avg_daily // 3600, (avg_daily % 3600) // 60
print(f" Active days: {total_days}")
print(f" Average daily watch time: {avg_daily_h}h {avg_daily_m}m")
# Most active day
most_active_day, most_active_time = max(daily_data, key=lambda x: x[1])
print(
f" Most active day: {most_active_day} ({self.format_duration(most_active_time)})"
)
# Content analysis
print(f"\n🎬 CONTENT ANALYSIS:")
most_watched = self.most_watched_content(5)
for i, (title, watch_time) in enumerate(most_watched, 1):
print(f" {i}. {title[:50]}{'...' if len(title) > 50 else ''}")
print(f" Watch time: {self.format_duration(watch_time)}")
# Recent activity
print(f"\n📅 RECENT ACTIVITY:")
recent_sessions = sorted(
self.data, key=lambda x: x["Start Time"], reverse=True
)[:5]
for session in recent_sessions:
date_str = session["Start Time"].strftime("%Y-%m-%d %H:%M")
print(
f" {date_str}: {session['Title'][:40]}{'...' if len(session['Title']) > 40 else ''}"
)
print(
f" Progress: {session['Progress']:.1f}% ({self.format_duration(session['Watch Time'])})"
)
def print_detailed_analysis(self):
"""Print detailed analysis with charts"""
print("\n" + "=" * 60)
print("DETAILED ANALYSIS")
print("=" * 60)
# Weekly progress chart
print(f"\n📈 WEEKLY PROGRESS (Last 8 weeks):")
weekly_data = self.weekly_progress()
recent_weeks = weekly_data[-8:] if len(weekly_data) >= 8 else weekly_data
for week_start, watch_time in recent_weeks:
week_end = week_start + timedelta(days=6)
week_range = (
f"{week_start.strftime('%m/%d')} - {week_end.strftime('%m/%d')}"
)
duration_str = self.format_duration(watch_time)
print(f" Week of {week_range}: {duration_str}")
# Monthly progress chart
print(f"\n📊 MONTHLY PROGRESS:")
monthly_data = self.monthly_progress()
for month_start, watch_time in monthly_data:
month_name = month_start.strftime("%B %Y")
duration_str = self.format_duration(watch_time)
print(f" {month_name}: {duration_str}")
# Progress distribution
print(f"\n🎯 PROGRESS DISTRIBUTION:")
progress_ranges = {"0-25%": 0, "26-50%": 0, "51-75%": 0, "76-99%": 0, "100%": 0}
for row in self.data:
progress = row["Progress"]
if progress == 100:
progress_ranges["100%"] += 1
elif progress >= 76:
progress_ranges["76-99%"] += 1
elif progress >= 51:
progress_ranges["51-75%"] += 1
elif progress >= 26:
progress_ranges["26-50%"] += 1
else:
progress_ranges["0-25%"] += 1
for range_name, count in progress_ranges.items():
percentage = (count / len(self.data)) * 100 if self.data else 0
print(f" {range_name}: {count} sessions ({percentage:.1f}%)")
def export_summary(self, output_file):
"""Export summary to a text file"""
try:
with open(output_file, "w", encoding="utf-8") as f:
# Redirect stdout to file
import io
old_stdout = sys.stdout
sys.stdout = io.StringIO()
self.print_summary()
self.print_detailed_analysis()
output = sys.stdout.getvalue()
sys.stdout = old_stdout
f.write(output)
print(f"Summary exported to: {output_file}")
except Exception as e:
print(f"Error exporting summary: {e}")
def main():
parser = argparse.ArgumentParser(description="Analyze MPV Immersion Tracker data")
parser.add_argument(
"csv_file",
nargs="?",
default="data/immersion_log.csv",
help="Path to the CSV file (default: data/immersion_log.csv)",
)
parser.add_argument(
"--export", "-e", metavar="FILE", help="Export summary to specified file"
)
parser.add_argument(
"--detailed", "-d", action="store_true", help="Show detailed analysis"
)
args = parser.parse_args()
# Check if CSV file exists
if not os.path.exists(args.csv_file):
print(f"Error: CSV file '{args.csv_file}' not found!")
print("Make sure you have run the MPV immersion tracker first.")
sys.exit(1)
# Create analyzer and run analysis
analyzer = ImmersionAnalyzer(args.csv_file)
if args.export:
analyzer.export_summary(args.export)
else:
analyzer.print_summary()
if args.detailed:
analyzer.print_detailed_analysis()
print(f"\n💡 Tip: Use --export FILENAME to save the analysis to a file")
print(f"💡 Tip: Use --detailed for more comprehensive analysis")
if __name__ == "__main__":
main()

21
immersion-tracker.conf Normal file
View File

@@ -0,0 +1,21 @@
start_tracking_key=ctrl+t
data_dir=~/.config/mpv/scripts/immersion-tracker/data
csv_file=~/.config/mpv/scripts/immersion-tracker/data/immersion_log.csv
session_file=~/.config/mpv/scripts/immersion-tracker/data/current_session.json
min_session_duration=30
save_interval=10
enable_debug_logging=no
backup_sessions=no
max_backup_files=10
use_title=yes
use_filename=no
custom_prefix=[Immersion]
max_title_length = 100
export_csv=yes
export_json=no
export_html=no
backup_csv=yes
show_session_start=yes
show_session_end=yes
show_progress_milestones=no
milestone_percentages=25507590

424
main.lua Normal file
View File

@@ -0,0 +1,424 @@
-- MPV Immersion Tracker for Language Learning
-- This script tracks watching sessions for language learning content
-- Author: Generated for language learning immersion tracking
local mp = require("mp")
local utils = require("mp.utils")
local script_dir = mp.get_script_directory()
-- Configuration using MPV options
mp.options = require("mp.options")
local options = {
-- Keybinding settings
start_tracking_key = "ctrl+t",
-- File paths (relative to script directory)
data_dir = script_dir .. "/data",
csv_file = script_dir .. "/data/immersion_log.csv",
session_file = script_dir .. "/data/current_session.json",
-- Tracking settings
min_session_duration = 30, -- seconds
save_interval = 10, -- seconds
-- Advanced settings
enable_debug_logging = false,
backup_sessions = true,
max_backup_files = 10,
-- Session naming preferences
use_title = true, -- Use media title for session names
use_filename = false, -- Use filename instead
custom_prefix = "[Immersion] ", -- Add custom prefix to session names
max_title_length = 100, -- Maximum title length
-- Export settings
export_csv = true, -- Export to CSV
export_json = false, -- Export to JSON
export_html = false, -- Export to HTML report
backup_csv = true, -- Create backup CSV files
-- Notification settings
show_session_start = true, -- Show OSD message when session starts
show_session_end = true, -- Show OSD message when session ends
show_progress_milestones = false, -- Show progress milestone messages
milestone_percentages = "25,50,75,90", -- Comma-separated percentages
}
mp.options.read_options(options, "immersion-tracker")
-- Parse milestone percentages from string to table
local function parse_milestone_percentages()
local percentages = {}
for percentage in options.milestone_percentages:gmatch("([^,]+)") do
local num = tonumber(percentage:match("^%s*(.-)%s*$"))
if num and num >= 0 and num <= 100 then
table.insert(percentages, num)
end
end
return percentages
end
options.milestone_percentages = parse_milestone_percentages()
-- Global variables
local current_session = nil
local session_start_time = nil
local last_save_time = 0
local video_info = {}
local is_tracking = false
-- Utility functions
local function log(message)
if options.enable_debug_logging then
mp.msg.info("[Immersion Tracker] " .. message)
else
mp.msg.info("[Immersion Tracker] " .. message)
end
end
local function ensure_data_directory()
local dir = options.data_dir
local result = utils.subprocess({
args = { "mkdir", "-p", dir },
cancellable = false,
})
if result.status ~= 0 then
log("Failed to create data directory: " .. dir)
end
end
local function get_current_timestamp()
return os.time()
end
local function format_timestamp(timestamp)
return os.date("%Y-%m-%d %H:%M:%S", timestamp)
end
local function get_duration_string(seconds)
local hours = math.floor(seconds / 3600)
local minutes = math.floor((seconds % 3600) / 60)
local secs = seconds % 60
return string.format("%02d:%02d:%02d", hours, minutes, secs)
end
-- File operations
local function save_session_to_file()
if not current_session then
return
end
local session_file = io.open(options.session_file, "w")
if session_file then
session_file:write(utils.format_json(current_session))
session_file:close()
end
end
local function load_existing_session()
local session_file = io.open(options.session_file, "r")
if session_file then
local content = session_file:read("*all")
session_file:close()
if content and content ~= "" then
local success, session = pcall(utils.parse_json, content)
if success and session then
current_session = session
session_start_time = session.start_time
is_tracking = true
log("Resumed existing session: " .. session.title)
return true
end
end
end
return false
end
local function save_session_to_csv()
if not current_session then
return
end
local csv_path = options.csv_file
log("Saving session to CSV: " .. csv_path)
-- Create CSV header if file doesn't exist
local file_exists = io.open(csv_path, "r")
local need_header = not file_exists
if file_exists then
file_exists:close()
end
local csv_file = io.open(csv_path, "a")
if not csv_file then
log("Failed to open CSV file for writing")
return
end
-- Write header if needed
if need_header then
csv_file:write(
"Session ID,Title,Filename,Path,Duration,Start Time,End Time,Watch Time,Progress,Video Format,Audio Format,Resolution,FPS,Bitrate\n"
)
end
-- Write session data
local csv_line = string.format(
'"%s","%s","%s","%s",%d,"%s","%s",%d,%.2f,"%s","%s","%s","%s","%s"\n',
current_session.id,
current_session.title:gsub('"', '""'),
current_session.filename:gsub('"', '""'),
current_session.path:gsub('"', '""'),
current_session.duration,
current_session.start_timestamp,
current_session.end_timestamp,
current_session.total_watch_time,
current_session.watch_progress,
current_session.video_format,
current_session.audio_format,
current_session.resolution,
current_session.fps,
current_session.bitrate
)
csv_file:write(csv_line)
csv_file:close()
log("Session saved to CSV: " .. current_session.title)
end
-- Video information gathering
local function gather_video_info()
video_info = {
filename = mp.get_property("filename") or "unknown",
path = mp.get_property("path") or "unknown",
title = mp.get_property("media-title") or mp.get_property("filename") or "unknown",
duration = mp.get_property_number("duration") or 0,
video_format = mp.get_property("video-codec") or "unknown",
audio_format = mp.get_property("audio-codec") or "unknown",
resolution = mp.get_property("video-params/width")
and mp.get_property("video-params/height")
and mp.get_property("video-params/width") .. "x" .. mp.get_property("video-params/height")
or "unknown",
fps = mp.get_property("video-params/fps") or "unknown",
bitrate = mp.get_property("video-bitrate") or "unknown",
}
log("Video info gathered: " .. video_info.title)
end
-- Session management
local function start_new_session()
if current_session then
log("Session already in progress, ending previous session first")
end_current_session()
end
-- Gather current video info
gather_video_info()
-- Determine session title based on options
local session_title = video_info.title
if options.use_filename then
session_title = video_info.filename
end
-- Apply custom prefix and length limit
if options.custom_prefix then
session_title = options.custom_prefix .. session_title
end
if #session_title > options.max_title_length then
session_title = session_title:sub(1, options.max_title_length) .. "..."
end
current_session = {
id = os.time() .. "_" .. math.random(1000, 9999),
filename = video_info.filename,
path = video_info.path,
title = session_title,
duration = video_info.duration,
start_time = get_current_timestamp(),
start_timestamp = format_timestamp(get_current_timestamp()),
video_format = video_info.video_format,
audio_format = video_info.audio_format,
resolution = video_info.resolution,
fps = video_info.fps,
bitrate = video_info.bitrate,
watch_progress = 0,
last_position = 0,
}
session_start_time = get_current_timestamp()
is_tracking = true
save_session_to_file()
log("New immersion session started: " .. current_session.title)
-- Show on-screen message if enabled
if options.show_session_start then
mp.osd_message("Immersion tracking started: " .. current_session.title, 3)
end
end
local function update_session_progress()
if not current_session or not is_tracking then
return
end
local current_pos = mp.get_property_number("time-pos") or 0
local duration = mp.get_property_number("duration") or 0
if duration > 0 then
current_session.watch_progress = (current_pos / duration) * 100
current_session.last_position = current_pos
-- Check for milestone notifications
if options.show_progress_milestones then
for _, milestone in ipairs(options.milestone_percentages) do
if
current_session.watch_progress >= milestone
and (not current_session.milestones_reached or not current_session.milestones_reached[milestone])
then
if not current_session.milestones_reached then
current_session.milestones_reached = {}
end
current_session.milestones_reached[milestone] = true
mp.osd_message(string.format("Progress milestone: %d%%", milestone), 2)
log("Progress milestone reached: " .. milestone .. "%")
end
end
end
end
-- Auto-save if enough time has passed
local current_time = get_current_timestamp()
if current_time - last_save_time >= options.save_interval then
save_session_to_file()
last_save_time = current_time
end
end
local function end_current_session()
if not current_session or not is_tracking then
return
end
local end_time = get_current_timestamp()
current_session.end_time = end_time
current_session.end_timestamp = format_timestamp(end_time)
current_session.total_watch_time = end_time - session_start_time
current_session.watch_progress = (current_session.last_position / current_session.duration) * 100
-- Save to CSV if enabled
if options.export_csv then
save_session_to_csv()
end
log(
"Session ended: "
.. current_session.title
.. " (Progress: "
.. string.format("%.1f", current_session.watch_progress)
.. "%)"
)
-- Show on-screen message if enabled
if options.show_session_end then
mp.osd_message(
"Immersion tracking ended: " .. string.format("%.1f", current_session.watch_progress) .. "% completed",
3
)
end
current_session = nil
session_start_time = nil
is_tracking = false
-- Clean up session file
local session_file = io.open(options.session_file, "w")
if session_file then
session_file:write("")
session_file:close()
end
end
-- Keybinding handler
local function toggle_tracking()
if is_tracking then
log("Stopping immersion tracking...")
end_current_session()
else
log("Starting immersion tracking...")
start_new_session()
end
end
-- Event handlers
local function on_file_loaded()
log("File loaded, ready for manual tracking")
-- Try to load existing session if available
load_existing_session()
end
local function on_file_end()
if current_session and is_tracking then
log("File ended, completing session...")
end_current_session()
end
end
local function on_shutdown()
if current_session and is_tracking then
log("MPV shutting down, saving session...")
end_current_session()
end
end
local function on_seek()
if current_session and is_tracking then
update_session_progress()
end
end
local function on_time_update()
if current_session and is_tracking then
update_session_progress()
end
end
-- Initialize script
local function init()
log("Immersion Tracker initialized")
log("Configuration loaded:")
log(" Keybinding: " .. options.start_tracking_key)
log(" Data directory: " .. options.data_dir)
log(" Save interval: " .. options.save_interval .. " seconds")
log(" Debug logging: " .. (options.enable_debug_logging and "enabled" or "disabled"))
ensure_data_directory()
-- Register keybinding
mp.remove_key_binding("toggle-clipboard-insertion")
mp.add_key_binding(options.start_tracking_key, "immersion_tracking", toggle_tracking)
-- Register event handlers
mp.register_event("file-loaded", on_file_loaded)
mp.register_event("end-file", on_file_end)
mp.register_event("shutdown", on_shutdown)
-- Register property change handlers
mp.observe_property("time-pos", "number", on_time_update)
-- Register seek event
mp.register_event("seek", on_seek)
log("Event handlers registered successfully")
log("Press " .. options.start_tracking_key .. " to start/stop immersion tracking")
end
-- Start the script
init()