syncfm-core synchronizes missing scrobbles between Last.fm, Libre.fm, and
GNU.fm-compatible networks.
- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| src/syncfm_core | ||
| .gitignore | ||
| .python-version | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
syncfm-core
syncfm-core synchronizes missing scrobbles between Last.fm, Libre.fm, and
GNU.fm-compatible networks. It includes both a reusable, typed Python
application layer and the syncfm command-line interface.
The project is currently beta software. Keep a copy of important listening history before using it with an account you cannot easily restore.
Features
- Directional synchronization from a source profile to a target profile
- Last.fm, Libre.fm, and custom GNU.fm-compatible instances
- Optional start and end timestamps for bounded synchronization
- Durable SQLite sessions that can be inspected, resumed, or discarded
- Metadata enrichment and retries for transient network failures
- Reusable TOML profiles with credentials in the system keyring by default
- HTTP, HTTPS, SOCKS5, and SOCKS5h proxy support per profile
- Interactive terminal progress dashboard with a plain-text fallback
- Strictly typed public Python models, protocols, services, and exceptions
Requirements
- Python 3.12 or newer
- API credentials for every configured network
- A working system keyring when using the default credential storage
uvfor the development workflow shown below
Installation
Install the released command directly from the Forgejo repository with uv:
uv tool install "syncfm-core @ git+https://git.hirad.it/Hirad/syncfm-core.git@0.1.0"
syncfm --help
To run the current development version from a checkout:
git clone https://git.hirad.it/Hirad/syncfm-core.git
cd syncfm-core
uv sync
uv run syncfm --help
In the examples below, prefix syncfm with uv run when working from a
checkout instead of an installed tool.
Quick start
1. Create network profiles
Create one profile for each account. Profile names must start with a lowercase letter or number and may contain lowercase letters, numbers, underscores, and hyphens.
syncfm profile add lastfm-main \
--type lastfm \
--username alice \
--api-key YOUR_LASTFM_API_KEY
The command securely prompts for the API secret and account password. Create a Libre.fm profile in the same way:
syncfm profile add librefm-main \
--type librefm \
--username alice \
--api-key YOUR_LIBREFM_API_KEY
GNU.fm profiles also require the instance hostname:
syncfm profile add community \
--type gnufm \
--hostname music.example.org \
--username alice \
--api-key YOUR_API_KEY
Use --use-session-key to enter an existing authenticated session key instead
of a password. Existing profiles are protected from accidental replacement;
pass --force to replace one intentionally.
Review stored profiles without exposing their credentials:
syncfm profile list
syncfm profile show lastfm-main
2. Synchronize scrobbles
Copy scrobbles that exist on the source but are missing from the target:
syncfm sync --source lastfm-main --target librefm-main
Synchronization is directional. Reversing --source and --target performs a
different operation.
By default, the start timestamp is inferred from the target's latest scrobble
and the end timestamp is the current time. If the target has no scrobbles, you
must provide --start. Both boundaries are inclusive Unix timestamps:
syncfm sync \
--source lastfm-main \
--target librefm-main \
--start 1704067200 \
--end 1735689599
Before submitting anything, SyncFM fetches source and target snapshots, compares them, and enriches missing items with available source metadata.
3. Recover interrupted work
Sync state is persisted in SQLite. A new sync will not start while unfinished work exists.
syncfm status
syncfm resume SESSION_ID
The original profile names are stored with new sessions, so only the session ID is normally needed to resume. To abandon an unfinished session and delete its stored state:
syncfm discard SESSION_ID
Both discard and profile remove ask for confirmation. Use --yes only in
non-interactive workflows where the target is already known.
Profiles, credentials, and proxies
Profiles are stored in a versioned TOML file. Sensitive values use the system keyring by default:
syncfm profile add my-profile \
--type lastfm \
--username alice \
--api-key YOUR_API_KEY \
--secret-storage keyring
--secret-storage file stores the API secret and password or session key
directly in the TOML configuration file. This is useful in carefully protected
headless environments, but the values are plaintext; restrict access to that
file and do not commit it.
Configure a proxy independently for either profile:
syncfm profile add proxied-account \
--type lastfm \
--username alice \
--api-key YOUR_API_KEY \
--proxy socks5h://127.0.0.1:1080
Supported proxy schemes are http, https, socks5, and socks5h. A value
without a scheme, such as 127.0.0.1:1080, defaults to socks5.
Paths and environment variables
SyncFM uses platform-appropriate user directories. Run syncfm --help to see
the resolved defaults on the current system. Override them with global options:
syncfm \
--config /path/to/config.toml \
--database /path/to/syncfm.sqlite3 \
sync --source source-profile --target target-profile
The CLI recognizes these environment variables:
| Variable | Purpose |
|---|---|
SYNCFM_CONFIG_PATH |
TOML profile configuration path |
SYNCFM_DATABASE_PATH |
SQLite synchronization-state path |
SYNCFM_SOURCE_PROFILE |
Default source profile for sync or legacy resume |
SYNCFM_TARGET_PROFILE |
Default target profile for sync or legacy resume |
SYNCFM_PROFILE_API_SECRET |
API secret used by profile add |
SYNCFM_PROFILE_PASSWORD |
Password used by profile add |
SYNCFM_PROFILE_SESSION_KEY |
Session key used with --use-session-key |
Secret environment variables avoid interactive prompts but may be visible to other processes or retained in shell and automation configuration. Use the secret mechanism provided by your operating system or CI platform.
Pass --verbose (or -v) before the subcommand to enable diagnostic logging:
syncfm --verbose status
Python API
The CLI is a frontend for the same public application services available to Python callers:
from pathlib import Path
from syncfm_core import (
NetworkFactory,
SyncRequest,
create_profile_service,
create_sync_service,
)
profiles = create_profile_service()
factory = NetworkFactory()
source = factory.create(profiles.resolve("lastfm-main"))
target = factory.create(profiles.resolve("librefm-main"))
service = create_sync_service(Path("syncfm.sqlite3"))
result = service.sync(
source,
target,
SyncRequest(start_timestamp=1704067200),
source_profile="lastfm-main",
target_profile="librefm-main",
)
print(f"Synchronized {result.synced_count} scrobbles")
print(f"Failed: {result.failed_count}")
create_sync_service accepts an optional RetryPolicy and progress callback.
The package also exports its domain models, network protocols, profile service,
and structured exception hierarchy for custom frontends and integrations.
Command reference
syncfm sync Start a synchronization
syncfm resume Resume an unfinished session
syncfm status List unfinished sessions
syncfm discard Delete an unfinished session
syncfm profile add
syncfm profile list
syncfm profile show
syncfm profile remove
Use syncfm COMMAND --help for all arguments and options.
Development
Create the environment and run the project checks:
uv sync
uv run ruff check .
uv run pyright
uv build
Please report bugs and feature requests in the issue tracker. Additional documentation is available in the project wiki.
License
syncfm-core is licensed under the
GNU General Public License v3.0 or later.