A full-featured, modular desktop productivity timer built with Python and Tkinter. No internet connection required to run — music and sounds are stored locally after the first import.
- Work/break timer with short break, long break, and full cycle modes
- Drift-free countdown using
time.monotonic()— stays accurate under any CPU load or sleep/wake cycle - Pause & resume — resumes exactly where you left off
- Compact mode — non-essential controls hide automatically when the window is narrowed
- Always on top — optional setting to keep the timer above other windows
- Language switcher — toggle between 🇪🇸 Spanish and 🇬🇧 English at any time; preference is saved between sessions
- Session history — every completed session is stored locally as JSON
- Weekly statistics with a bar chart, daily breakdown, and task list
- Streak tracking — counts consecutive days of use, resets automatically if a day is missed
- CSV export — export your full history for analysis in Excel or similar tools
- Configurable durations and cycles through a clean settings tab
- Autostart for breaks and next Pomodoros
- Tick-tock sound — optional metronome click during work and/or break sessions, toggled independently
- Custom alarm sound — supports MP3, WAV, OGG, FLAC, M4A, OPUS, AAC; files are copied to a dedicated local folder
- Music player — built-in background music player with persistent library, YouTube import, progress bar, and volume control; music pauses automatically when the alarm fires and resumes from the same position once the dialog is dismissed
- Python 3.10 or higher
pip(included with Python)ffmpeg— required for YouTube audio import (download, must be available in your system PATH)
# 1. Clone the repository
git clone https://github.com/HenryXCC/FlowTimer.git
cd flowtimer
# 2. (Recommended) Create a virtual environment
python -m venv .venv
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windows
# 3. Install dependencies
pip install -r requirements.txtpython main.pyNo configuration needed — the app creates its data file (~/.pomodoro_data.json) automatically on first run.
A pre-built standalone executable for Windows is available on the Releases page — no Python or pip installation needed.
| Platform | File | Status |
|---|---|---|
| Windows | FlowTimer.exe |
✅ Available |
| macOS | FlowTimer |
🔧 Build from source |
| Linux | FlowTimer |
🔧 Build from source |
macOS and Linux users can build their own executable from source using the instructions below.
pip install pyinstaller
# Windows
pyinstaller --onedir --windowed --clean --noconfirm --name "FlowTimer" --icon="assets/icon.ico" --add-data "assets/icon.ico;assets" --collect-all pygame main.py
# macOS / Linux
pyinstaller --onedir --windowed --clean --noconfirm --name "FlowTimer" --icon="assets/icon.ico" --add-data "assets/icon.ico:assets" --collect-all pygame main.pyThe output is a dist/FlowTimer/ folder containing FlowTimer.exe (Windows) or FlowTimer (macOS/Linux) alongside a _internal/ folder with all dependencies. Distribute the entire dist/FlowTimer/ folder — the executable will not run without _internal/.
The core/ package is fully covered by a pytest test suite (no GUI required).
# Install pytest if you haven't already
pip install pytest
# Run all tests from the project root
pytest tests/All tests run without a display or audio device — UI code and pygame are fully mocked.
pomodoro-timer/
│
├── main.py # Entry point — run this
│
├── core/ # Pure-Python business logic (no Tkinter)
│ ├── __init__.py
│ ├── constants.py # Colours, paths, default configuration values
│ ├── i18n.py # Internationalisation — STRINGS dict + t() helper
│ ├── audio.py # GestorAudio — alarm, tick-tock, and music playback
│ ├── storage.py # JSON persistence (cargar_datos / guardar_datos)
│ └── stats.py # estadisticas_semana(), exportar_csv()
│
├── ui/ # Tkinter views (depend on core, not on each other)
│ ├── __init__.py
│ ├── app.py # App(tk.Tk) — root window, timer state, coordinator
│ ├── tab_timer.py # TabTimer — clock ring, controls, daily summary
│ ├── tab_stats.py # TabStats — chart, history table, CSV export
│ └── tab_settings.py # TabSettings — durations, options, alarm, music player
│
├── tests/ # pytest test suite
│ ├── test_audio.py # GestorAudio state, position tracking, alarm/music interaction
│ ├── test_chart_language_bug.py # Regression: chart day-key / language consistency
│ ├── test_i18n.py # Language switching, key parity, formatting
│ ├── test_stats.py # Weekly statistics and CSV export
│ └── test_storage.py # JSON persistence and forward-compatibility
│
├── assets/ # Icons and screenshots
├── requirements.txt
├── .gitignore
├── LICENSE
└── README.md
The app also creates these paths at runtime (outside the project folder):
| Path | Purpose |
|---|---|
~/.pomodoro_data.json |
Session history, streak, configuration, language, and music library |
~/.pomodoro_sounds/ |
Copied alarm audio files |
~/.pomodoro_music/ |
Copied and downloaded music files (MP3, thumbnails) |
| Package | Purpose | Required for |
|---|---|---|
tkinter |
GUI framework | Everything — ships with Python |
pygame |
Audio playback | Alarm sounds, tick-tock, music player |
yt-dlp |
YouTube audio download | Music player — YouTube import |
Pillow |
Image loading | Music player — YouTube thumbnails |
ffmpeg |
Audio conversion | Music player — YouTube import (system tool, not pip) |
All pip packages are listed in requirements.txt and installed in one step. tkinter ships with Python. ffmpeg must be installed separately and available in your system PATH.
The built-in music player lives in the Settings tab and supports two ways to add music:
Click ➕ File to import one or more audio files (MP3, WAV, OGG, FLAC, M4A, OPUS, AAC), or use the 📁 button to import an entire folder at once (up to 50 files). Files are copied to ~/.pomodoro_music/ so they remain available even if the originals are moved or deleted.
Click ▶ YouTube, paste up to 3 URLs (one per field), and click Import. The app downloads the audio as MP3 via yt-dlp and ffmpeg, fetches the video thumbnail, and adds the track to the library — all in the background while you keep using the timer.
ffmpegmust be installed and in your PATH for YouTube import to work.
When a Pomodoro or break ends, music pauses immediately before the alarm sounds. Once you dismiss the dialog, music resumes automatically from the exact position where it stopped — no manual action needed.
The app ships with full Spanish and English support. The active language can be changed at any time with the 🌐 EN / 🌐 ES button in the top-right corner. The choice persists between sessions.
All user-visible strings live in core/i18n.py as a nested dictionary:
STRINGS = {
"es": { "btn_start": "▶ Iniciar", ... },
"en": { "btn_start": "▶ Start", ... },
}The t() helper translates any key in the active language with optional format arguments:
from core.i18n import t, set_language
set_language("en")
t("btn_start") # "▶ Start"
t("lbl_streak", n=5) # "🔥 Streak: 5 days"- Open
core/i18n.py. - Add a new entry to
STRINGSusing an existing language as a template. - Call
set_language("xx")where"xx"is your new code.
No other file needs to change.
core/ is completely free of Tkinter — all modules in that package are independently testable. The UI layer (ui/) imports from core/, but the three tab frames never import each other; they communicate exclusively through the App coordinator:
App.data — shared session/config/music dict
App.cfg — shortcut to App.data["config"]
App.audio — GestorAudio instance
App.cambiar_modo()
App.toggle_start() / reset() / saltar()
App.on_config_saved()
App.on_historial_borrado()
The countdown uses time.monotonic() as its reference clock. The UI refreshes every 100 ms, but remaining time is recomputed from the wall clock on every tick — the timer never drifts regardless of CPU load.
GestorAudio manages three independent audio concerns:
- Alarm — plays a custom sound file via
pygame.mixer.Soundormixer.music, auto-stopped after 20 seconds; falls back to a system beep if pygame is unavailable. - Tick-tock — a soft click generated in pure Python (no audio file needed) looped on a daemon thread, one click per second.
- Music — streams via
pygame.mixer.musicwith volume control, loop-forever playback, and position tracking via an offset accumulator (_music_pos_offset_ms) so the displayed progress and seek position remain accurate across pause/resume and alarm cycles.
When an alarm fires, the current playback position is saved, music is paused, and the alarm plays. After the user dismisses the dialog, music_restaurar() reloads the file and resumes from the saved position in a single synchronous call — avoiding race conditions between the alarm thread and the UI thread.
Session data is stored in a single JSON file. The schema is forward-compatible: new keys are back-filled via setdefault, so old data files are never broken. The music library (music_tracks) is stored in the same file as a list of track objects:
{
"name": "LoFi Beats",
"path": "/home/user/.pomodoro_music/abc123.mp3",
"source": "youtube",
"youtube_id": "abc123",
"channel": "Example Channel",
"duration_s": 3600,
"thumb_path": "/home/user/.pomodoro_music/abc123.png"
}The streak counter increments only if the app was last used yesterday. If more than one day has passed, the streak resets to 1 on the new session — consistent with apps like Duolingo.
| Setting | Default | Range |
|---|---|---|
| Pomodoro duration | 25 min | 1–120 min |
| Short break | 5 min | 1–30 min |
| Long break | 15 min | 1–60 min |
| Pomodoros per cycle | 4 | 1–10 |
| Sound on finish | ✅ | — |
| Auto-start break | ❌ | — |
| Auto-start next Pomodoro | ❌ | — |
| Always on top | ❌ | — |
| Tick-tock during work | ❌ | — |
| Tick-tock during breaks | ❌ | — |
| Custom alarm sound | None | MP3 / WAV / OGG / FLAC / M4A / OPUS / AAC |
| Music volume | 70% | 0–100% |
Duration changes apply from the next session. All other changes apply immediately on save.
Distributed under the MIT License. See LICENSE for details.
Un temporizador de escritorio completo y modular para productividad, construido con Python y Tkinter. No requiere conexión a internet para funcionar — la música y los sonidos se almacenan localmente después de la primera importación.
- Temporizador con modos de descanso corto, descanso largo y ciclo completo
- Cuenta regresiva sin drift usando
time.monotonic()— preciso bajo cualquier carga de CPU o ciclos de suspensión - Pausar y reanudar — continúa exactamente donde lo dejaste
- Modo compacto — los controles no esenciales se ocultan automáticamente al reducir el tamaño de la ventana
- Siempre visible — opción para mantener el temporizador por encima de otras ventanas
- Selector de idioma — cambia entre 🇪🇸 Español y 🇬🇧 Inglés en cualquier momento; la preferencia se guarda entre sesiones
- Historial de sesiones — cada sesión completada se guarda localmente en JSON
- Estadísticas semanales con gráfico de barras, desglose diario y lista de tareas
- Racha de días — cuenta días consecutivos de uso y se reinicia automáticamente si se pierde un día
- Exportación CSV — exporta tu historial completo para análisis en Excel o herramientas similares
- Duraciones y ciclos configurables desde una pestaña de ajustes limpia
- Inicio automático de descansos y siguientes Pomodoros
- Sonido de tictac — clic de metrónomo opcional durante sesiones de trabajo y/o descanso, con activación independiente
- Sonido de alarma personalizado — soporta MP3, WAV, OGG, FLAC, M4A, OPUS, AAC; los archivos se copian a una carpeta local dedicada
- Reproductor de música — reproductor integrado con biblioteca persistente, importación desde YouTube, barra de progreso y control de volumen; la música se pausa automáticamente al sonar la alarma y se reanuda desde la misma posición al cerrar el diálogo
- Python 3.10 o superior
pip(incluido con Python)ffmpeg— requerido para importar audio desde YouTube (descargar, debe estar disponible en el PATH del sistema)
# 1. Clonar el repositorio
git clone https://github.com/HenryXCC/FlowTimer.git
cd flowtimer
# 2. (Recomendado) Crear un entorno virtual
python -m venv .venv
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windows
# 3. Instalar dependencias
pip install -r requirements.txtpython main.pyNo se necesita configuración previa — la app crea su archivo de datos (~/.pomodoro_data.json) automáticamente en el primer uso.
En la página de Releases se encuentra el ejecutable listo para usar en Windows — sin necesidad de instalar Python ni pip.
| Plataforma | Archivo | Estado |
|---|---|---|
| Windows | FlowTimer.exe |
✅ Disponible |
| macOS | FlowTimer |
🔧 Compilar desde el código |
| Linux | FlowTimer |
🔧 Compilar desde el código |
pip install pyinstaller
# Windows
pyinstaller --onedir --windowed --clean --noconfirm --name "FlowTimer" --icon="assets/icon.ico" --add-data "assets/icon.ico;assets" --collect-all pygame main.py
# macOS / Linux
pyinstaller --onedir --windowed --clean --noconfirm --name "FlowTimer" --icon="assets/icon.ico" --add-data "assets/icon.ico:assets" --collect-all pygame main.pyEl resultado es una carpeta dist/FlowTimer/ que contiene FlowTimer.exe (Windows) o FlowTimer (macOS/Linux) junto con una carpeta _internal/ con todas las dependencias. Distribuí la carpeta dist/FlowTimer/ completa — el ejecutable no funciona sin _internal/.
El paquete core/ tiene cobertura completa con una suite de pytest (no requiere GUI).
# Instalar pytest si aún no lo tenés
pip install pytest
# Ejecutar todos los tests desde la raíz del proyecto
pytest tests/Todos los tests corren sin pantalla ni dispositivo de audio — el código de UI y pygame están completamente mockeados.
pomodoro-timer/
│
├── main.py # Punto de entrada — ejecutar este archivo
│
├── core/ # Lógica de negocio en Python puro (sin Tkinter)
│ ├── __init__.py
│ ├── constants.py # Colores, rutas, valores de configuración por defecto
│ ├── i18n.py # Internacionalización — diccionario STRINGS + helper t()
│ ├── audio.py # GestorAudio — alarma, tictac y reproducción de música
│ ├── storage.py # Persistencia JSON (cargar_datos / guardar_datos)
│ └── stats.py # estadisticas_semana(), exportar_csv()
│
├── ui/ # Vistas Tkinter (dependen de core, no entre sí)
│ ├── __init__.py
│ ├── app.py # App(tk.Tk) — ventana raíz, estado del timer, coordinador
│ ├── tab_timer.py # TabTimer — anillo del reloj, controles, resumen diario
│ ├── tab_stats.py # TabStats — gráfico, tabla de historial, exportar CSV
│ └── tab_settings.py # TabSettings — duraciones, opciones, alarma, reproductor
│
├── tests/ # Suite de tests con pytest
│ ├── test_audio.py # Estado de GestorAudio, posición, interacción alarma/música
│ ├── test_chart_language_bug.py # Regresión: consistencia claves de días / idioma en gráfico
│ ├── test_i18n.py # Cambio de idioma, paridad de claves, formateo
│ ├── test_stats.py # Estadísticas semanales y exportación CSV
│ └── test_storage.py # Persistencia JSON y compatibilidad hacia adelante
│
├── assets/ # Íconos y capturas de pantalla
├── requirements.txt
├── .gitignore
├── LICENSE
└── README.md
La app también crea estas rutas en tiempo de ejecución (fuera de la carpeta del proyecto):
| Ruta | Propósito |
|---|---|
~/.pomodoro_data.json |
Historial, racha, configuración, idioma y biblioteca de música |
~/.pomodoro_sounds/ |
Archivos de alarma copiados |
~/.pomodoro_music/ |
Archivos de música copiados y descargados (MP3, miniaturas) |
| Paquete | Propósito | Necesario para |
|---|---|---|
tkinter |
Framework de GUI | Todo — viene con Python |
pygame |
Reproducción de audio | Alarmas, tictac, reproductor de música |
yt-dlp |
Descarga de audio de YouTube | Reproductor — importación desde YouTube |
Pillow |
Carga de imágenes | Reproductor — miniaturas de YouTube |
ffmpeg |
Conversión de audio | Reproductor — importación desde YouTube (herramienta del sistema, no pip) |
Todos los paquetes pip están en requirements.txt y se instalan en un solo paso. tkinter viene con Python. ffmpeg debe instalarse por separado y estar disponible en el PATH del sistema.
El reproductor integrado vive en la pestaña Configuración y admite dos formas de agregar música:
Hacé clic en ➕ Archivo para importar uno o más archivos de audio (MP3, WAV, OGG, FLAC, M4A, OPUS, AAC), o usá el botón 📁 para importar una carpeta completa de una vez (hasta 50 archivos). Los archivos se copian a ~/.pomodoro_music/ y quedan disponibles aunque los originales se muevan o eliminen.
Hacé clic en ▶ YouTube, pegá hasta 3 URLs (una por campo) y hacé clic en Importar. La app descarga el audio como MP3 mediante yt-dlp y ffmpeg, obtiene la miniatura del video y agrega la pista a la biblioteca — todo en segundo plano mientras seguís usando el timer.
ffmpegdebe estar instalado y en el PATH para que la importación desde YouTube funcione.
Cuando termina un Pomodoro o descanso, la música se pausa inmediatamente antes de que suene la alarma. Al cerrar el diálogo, la música se reanuda automáticamente desde la posición exacta donde se detuvo — sin acción manual.
La app incluye soporte completo para Español e Inglés. El idioma activo se puede cambiar en cualquier momento con el botón 🌐 EN / 🌐 ES en la esquina superior derecha. La elección persiste entre sesiones.
Todos los textos visibles al usuario viven en core/i18n.py como un diccionario anidado:
STRINGS = {
"es": { "btn_start": "▶ Iniciar", ... },
"en": { "btn_start": "▶ Start", ... },
}El helper t() traduce cualquier clave en el idioma activo con argumentos opcionales:
from core.i18n import t, set_language
set_language("es")
t("btn_start") # "▶ Iniciar"
t("lbl_streak", n=5) # "🔥 Racha: 5 días"- Abrí
core/i18n.py. - Agregá una nueva entrada en
STRINGSusando un idioma existente como plantilla. - Llamá a
set_language("xx")donde"xx"es tu nuevo código.
Ningún otro archivo necesita cambiar.
core/ no tiene dependencia alguna de Tkinter — todos los módulos son testeables de forma independiente. La capa de UI (ui/) importa desde core/, pero las pestañas nunca se importan entre sí; se comunican exclusivamente a través del coordinador App:
App.data — diccionario compartido de sesiones, config y música
App.cfg — atajo a App.data["config"]
App.audio — instancia de GestorAudio
App.cambiar_modo()
App.toggle_start() / reset() / saltar()
App.on_config_saved()
App.on_historial_borrado()
La cuenta regresiva usa time.monotonic() como reloj de referencia. La UI se refresca cada 100 ms, pero el tiempo restante se recalcula desde el reloj de pared en cada tick — el timer nunca se desvía sin importar la carga de CPU.
GestorAudio gestiona tres funciones de audio independientes:
- Alarma — reproduce un archivo de sonido personalizado via
pygame.mixer, detenida automáticamente a los 20 segundos; cae a un beep del sistema si pygame no está disponible. - Tictac — un clic suave generado en Python puro (sin archivo de audio) repetido en un hilo daemon, un clic por segundo.
- Música — transmite via
pygame.mixer.musiccon control de volumen, reproducción en bucle y seguimiento de posición mediante un acumulador de offset (_music_pos_offset_ms) para que el progreso mostrado y la posición de reanudación sean exactos a través de ciclos de pausa/alarma.
Al sonar la alarma, se guarda la posición actual, la música se pausa y la alarma suena. Al cerrar el diálogo, music_restaurar() recarga el archivo y reanuda desde la posición guardada en una llamada síncrona — evitando condiciones de carrera entre el hilo de la alarma y el hilo de la UI.
Los datos se guardan en un único archivo JSON compatible hacia adelante. La biblioteca de música (music_tracks) se almacena en el mismo archivo:
{
"name": "LoFi Beats",
"path": "/home/user/.pomodoro_music/abc123.mp3",
"source": "youtube",
"youtube_id": "abc123",
"channel": "Example Channel",
"duration_s": 3600,
"thumb_path": "/home/user/.pomodoro_music/abc123.png"
}El contador se incrementa solo si la app se usó ayer. Si pasó más de un día, la racha se reinicia a 1 — igual que Duolingo.
| Ajuste | Por defecto | Rango |
|---|---|---|
| Duración del Pomodoro | 25 min | 1–120 min |
| Descanso corto | 5 min | 1–30 min |
| Descanso largo | 15 min | 1–60 min |
| Pomodoros por ciclo | 4 | 1–10 |
| Sonido al terminar | ✅ | — |
| Iniciar descanso automáticamente | ❌ | — |
| Iniciar siguiente Pomodoro automáticamente | ❌ | — |
| Siempre visible | ❌ | — |
| Tictac durante el trabajo | ❌ | — |
| Tictac durante los descansos | ❌ | — |
| Sonido de alarma personalizado | Ninguno | MP3 / WAV / OGG / FLAC / M4A / OPUS / AAC |
| Volumen de música | 70% | 0–100% |
Los cambios de duración aplican desde la próxima sesión. Los demás cambios aplican de inmediato al guardar.
Distribuido bajo la Licencia MIT. Ver LICENSE para más detalles.





