syncfm-core synchronizes missing scrobbles between Last.fm, Libre.fm, and GNU.fm-compatible networks.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-14 10:40:22 +03:30
src/syncfm_core Enhance CLI progress and proxy support 2026-08-14 09:02:15 +03:30
.gitignore Initialize typed Python package 2026-08-08 14:57:56 +03:30
.python-version Initialize typed Python package 2026-08-08 14:57:56 +03:30
LICENSE Initial commit 2026-08-08 10:43:00 +00:00
pyproject.toml Mark project as beta 2026-08-14 10:37:28 +03:30
README.md Complete project README 2026-08-14 10:40:22 +03:30
uv.lock Enhance CLI progress and proxy support 2026-08-14 09:02:15 +03:30

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
  • uv for 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.