gendisc

Python versions PyPI - Version GitHub tag (with filter) License GitHub commits since latest release (by SemVer including pre-releases) CodeQL QA Tests Coverage Status Dependabot Documentation Status mypy uv pytest Ruff Downloads Stargazers pre-commit.ci status Prettier Follow @Tatsh Mastodon Follow

This tool generates scripts to create disc ISOs from a directory structure maximising use of the available space on each disc. This is useful for creating backups of large amounts of data that do not fit on a single disc. Directories whose capacity is larger than a single disc are split into multiple discs.

Commands

gendisc

Make a file listing filling up discs.

Usage

gendisc [OPTIONS] PATH

Options

--cross-fs

Allow crossing file systems.

-D, --drive <drive>

Drive path.

-d, --debug

Enable debug logging.

-i, --starting-index <INDEX>

Index to start with (defaults to 1).

-o, --output-dir <output_dir>

Output directory. Will be created if it does not exist.

-p, --prefix <prefix>

Prefix for volume ID and files.

-r, --delete

Unlink instead of sending to trash.

--no-labels

Do not create labels.

--cd-write-speed <cd_write_speed>

CD-R write speed.

--dvd-write-speed <dvd_write_speed>

DVD-R write speed.

--dvd-dl-write-speed <dvd_dl_write_speed>

DVD-R DL write speed.

--bd-write-speed <bd_write_speed>

BD-R write speed.

--bd-dl-write-speed <bd_dl_write_speed>

BD-R DL write speed.

--bd-tl-write-speed <bd_tl_write_speed>

BD-R TL write speed.

--bd-xl-write-speed <bd_xl_write_speed>

BD-R XL write speed.

--preparer <preparer>

Preparer string (128 characters).

--publisher <publisher>

Publisher string (128 characters).

Arguments

PATH

Required argument

Output

The output consists of a series of shell scripts (1 for each disc) that perform the following steps:

  • Generate the ISO image for the current set using mkisofs.

  • Save a SHA256 checksum of the image for verification.

  • Save a directory tree listing (requires tree).

  • Save a file listing using find.

  • Prompt to insert a blank disc. It will tell you the kind of disc to use.

  • Burn the image to disc using cdrecord.

  • Eject and re-insert the disc.

  • Verify the disc.

  • Delete or move the source files to the bin.

  • Eject the disc.

  • Prompt to move the disc to a label printer.

  • Open GIMP if you have it installed to the printer dialogue.

If you have mogrify (ImageMagick) and Inkscape installed, a label will be generated. This label can be opened in a tool that supports disc printing (such as GIMP). The image should be ready for printing (under Image Settings you should see it is exactly 12 cm at DPI 600).

Many of the steps above can be skipped by passing flags to the script. Currently the script supports these options:

Usage: script.sh [-h] [-G] [-K] [-k] [-O] [-P] [-s] [-S] [-V]
All flags default to no.
  -h: Show this help message.
  -G: Do not open GIMP on completion (if label file exists).
  -K: Keep ISO image after burning.
  -O: Only create ISO image.
  -P: Open GIMP in normal mode instead of batch mode.
  -S: Skip ejecting tray for blank disc (assume already inserted).
  -V: Skip verification of burnt disc.
  -k: Keep source files after burning.
  -s: Skip clean-up of .directory files.

Label generation

genlabel

Generate an image intended for printing on disc consisting of text in a spiral.

Usage

genlabel [OPTIONS] [TEXT]...

Options

-E, --end-theta <end_theta>

End theta.

-H, --height <height>

Height of the image.

-S, --space-per-loop <space_per_loop>

Space per loop.

-T, --start-theta <start_theta>

Start theta.

-V, --view-box <view_box>

SVG view box.

--dpi <dpi>

Dots per inch.

--keep-svg

When generating the PNG, keep the SVG file.

-c, --center <center>

Center of the spiral.

-d, --debug

Enable debug logging.

-f, --font-size <font_size>

Font size.

-g, --svg

Output SVG.

-o, --output <output>

Output file name.

-r, --start-radius <start_radius>

Start radius.

-t, --theta-step <theta_step>

Theta step.

-w, --width <width>

Width of the image.

Arguments

TEXT

Optional argument(s)

Library

Disc label generation.

exception gendisc.genlabel.MogrifyNotFound

Raised when mogrify is not found in PATH.

class gendisc.genlabel.Point(x: float, y: float)

Point class for SVG paths.

x : float

X coordinate.

y : float

Y coordinate.

gendisc.genlabel.create_spiral_path(center: Point | None = None, start_radius: float = 0, space_per_loop: float = 40, start_theta: SupportsFloat | SupportsIndex = -6840, end_theta: SupportsFloat | SupportsIndex = 0, theta_step: SupportsFloat | SupportsIndex = 30) → str

Get a path string for a spiral in an SVG file.

Defaults to creating a spiral that starts at the outside and goes inwards.

Algorithm borrowed from How to make a spiral in SVG?.

Parameters:
center : Point | None

The center of the spiral. If None, it defaults to Point(DEFAULT_WIDTH_HEIGHT, DEFAULT_WIDTH_HEIGHT).

start_radius : float

The starting radius of the spiral.

space_per_loop : float

The space between each loop of the spiral.

start_theta : float

The starting angle of the spiral in degrees. Use a negative number to have the spiral start at the outside and go inwards.

end_theta : float

The ending angle of the spiral in degrees.

theta_step : float

The step size of the angle in degrees.

Returns:

The path string for the spiral. Goes inside a <path> in the d attribute.

Return type:

str

gendisc.genlabel.create_spiral_text_svg(text: str, width: int = 800, height: int | None = None, view_box: tuple[int, int, int, int] | None = None, font_size: int = 32, center: Point | None = None, start_radius: float = 0, space_per_loop: float = 40, start_theta: SupportsFloat | SupportsIndex = -6840, end_theta: SupportsFloat | SupportsIndex = 0, theta_step: SupportsFloat | SupportsIndex = 30) → str

Create a spiral SVG text.

Defaults to creating a spiral that starts at the outside and goes inwards.

Parameters:
text : str

The text to put in the spiral.

width : int

The width of the SVG.

height : int | None

The height of the SVG. If None, width is used.

view_box : tuple[int, int, int, int] | None

The view box of the SVG. If None, it defaults to (0, 0, width * 2, height * 2).

font_size : int

The font size of the text in the SVG in pixels.

center : Point | None

The center of the spiral. If None, it defaults to Point(width, width).

start_radius : float

The starting radius of the spiral.

space_per_loop : float

The space between each loop of the spiral.

start_theta : float

The starting angle of the spiral in degrees. Use a negative number to have the spiral start at the outside and go inwards.

end_theta : float

The ending angle of the spiral in degrees.

theta_step : float

The step size of the angle in degrees.

Returns:

The SVG string for the spiral.

Return type:

str

gendisc.genlabel.line_intersection(m1: float, b1: float, m2: float, b2: float) → Point

Find the intersection of two lines.

Parameters:
m1 : float

Slope of the first line.

b1 : float

Y-intercept of the first line.

m2 : float

Slope of the second line.

b2 : float

Y-intercept of the second line.

Returns:

X and Y coordinates of the intersection point.

Return type:

Point

Raises:

ValueError – If the lines are parallel and do not intersect.

async gendisc.genlabel.write_spiral_text_png(filename: str | os.PathLike[str], text: str, width: int = 800, height: int | None = None, view_box: tuple[int, int, int, int] | None = None, dpi: int = 300, font_size: int = 32, center: Point | None = None, start_radius: float = 0, space_per_loop: float = 40, start_theta: _SupportsFloatOrIndex = -6840, end_theta: _SupportsFloatOrIndex = 0, theta_step: _SupportsFloatOrIndex = 30, *, keep: bool = False) → None

Write a spiral text SVG string to a file.

Defaults to creating a spiral that starts at the outside and goes inwards.

Requires mogrify from ImageMagick to be installed and in PATH.

Parameters:
filename : str | os.PathLike[str]

The filename to write the PNG to.

text : str

The text to put in the spiral.

width : int

The width of the SVG.

height : int | None

The height of the SVG. If None, width is used.

view_box : tuple[int, int, int, int] | None

The view box of the SVG. If not specified, it will be set to (0, 0, width * 2, height * 2).

dpi : int

Dots per inch used when rasterising the SVG.

font_size : int

The font size of the text in the SVG in pixels.

center : Point | None

The center of the spiral. If None, it defaults to Point(width, width).

start_radius : float

The starting radius of the spiral.

space_per_loop : float

The space between each loop of the spiral.

start_theta : float

The starting angle of the spiral in degrees.

end_theta : float

The ending angle of the spiral in degrees.

theta_step : float

The step size of the angle in degrees.

keep : bool

If True, keep the SVG file after conversion.

Raises:
async gendisc.genlabel.write_spiral_text_svg(filename: str | os.PathLike[str], text: str, width: int = 800, height: int | None = None, view_box: tuple[int, int, int, int] | None = None, font_size: int = 32, center: Point | None = None, start_radius: float = 0, space_per_loop: float = 40, start_theta: _SupportsFloatOrIndex = -6840, end_theta: _SupportsFloatOrIndex = 0, theta_step: _SupportsFloatOrIndex = 30) → None

Write a spiral text SVG string to a file.

Defaults to creating a spiral that starts at the outside and goes inwards.

Parameters:
filename : str | os.PathLike[str]

The filename to write the SVG to.

text : str

The text to put in the spiral.

width : int

The width of the SVG.

height : int | None

The height of the SVG. If None, width is used.

view_box : tuple[int, int, int, int] | None

The view box of the SVG. If not specified, it will be set to (0, 0, width * 2, height * 2).

font_size : int

The font size of the text in the SVG in pixels.

center : Point | None

The center of the spiral. If None, it defaults to Point(width, width).

start_radius : float

The starting radius of the spiral.

space_per_loop : float

The space between each loop of the spiral.

start_theta : float

The starting angle of the spiral in degrees.

end_theta : float

The ending angle of the spiral in degrees.

theta_step : float

The step size of the angle in degrees.

Typing helpers.

class gendisc.typing.AsyncStatusRun(*args, **kwargs)

Await a coroutine while optionally showing transient status (for example a spinner).

Implementations are supplied by the CLI layer so that gendisc.utils stays free of any specific console dependency.

async run(message: str, awaitable: Coroutine[Any, Any, T]) → T

Await awaitable while displaying message as status.

Parameters:
message : str

Short status text.

awaitable : collections.abc.Coroutine[Any, Any, T]

Coroutine to await.

Returns:

The value produced by awaitable.

Return type:

T

class gendisc.typing.SizeProgress(*args, **kwargs)

A progress reporter used by gendisc.utils.get_dir_size().

Implementations are supplied by the CLI layer so that gendisc.utils stays free of any specific progress-rendering dependency.

add_task(description: str, total: float | None = Ellipsis) → SizeProgressTask

Create a new task.

Parameters:
description : str

Human-readable description for the task.

total : float | None

Total number of steps, or None when indeterminate.

Returns:

The newly created task.

Return type:

SizeProgressTask

class gendisc.typing.SizeProgressTask(*args, **kwargs)

A single progress task handed out by SizeProgress.

advance(amount: float = 1) → None

Advance the task by amount steps.

Parameters:
amount : float

Number of steps to advance.

set_bounds(*, total: float, description: str | None = None) → None

Set a determinate total and optionally replace the task description.

Parameters:
total : float

Total step count for the task.

description : str | None

New description, or None to leave the description unchanged.

Utilities.

class gendisc.utils.DirectorySplitter(path: os.PathLike[str] | str, prefix: str, delete_command: str = 'trash', drive: os.PathLike[str] | str = '/dev/sr0', output_dir: os.PathLike[str] | str = '.', prefix_parts: tuple[str, ...] | None = None, preparer: str | None = None, publisher: str | None = None, starting_index: int = 1, progress: SizeProgress | None = None, write_speeds: WriteSpeeds | None = None, *, cross_fs: bool = False, labels: bool = False, mogrify_pool: MogrifyLabelPool | None = None, status_run: AsyncStatusRun | None = None)

Split directories into sets for burning to disc.

property sets : tuple[tuple[str, ...], ...]

Sets of entries produced by split(). Each inner tuple fits on one disc.

async split() → None

Split the directory into sets.

class gendisc.utils.MogrifyLabelPool(worker_count: int = 4)

Queue disc label jobs and process them with a fixed pool of concurrent workers.

Call start() before enqueueing work, then wait_until_finished() after all producers have awaited submit() so the process does not exit while mogrify work remains.

async start() → None

Start worker tasks.

Safe to call more than once; subsequent calls do nothing until wait_until_finished() has reset the pool.

async submit(label_file: PathLike[str] | str, spiral_text: str, *, fn_prefix: str) → None

Enqueue one label PNG job for the worker pool.

Parameters:
label_file : os.PathLike[str] | str

Destination path for the PNG.

spiral_text : str

Text for the spiral label.

fn_prefix : str

Short name for log messages (matches the volume file-name prefix).

async wait_until_finished() → None

Block until all submitted jobs complete, then stop workers.

Raises:

ValueError – If start() was never called.

class gendisc.utils.WriteSpeeds(cd: int = 24, dvd: int = 8, dvd_dl: float = 8, bd: int = 4, bd_dl: int = 6, bd_tl: int = 4, bd_xl: int = 4)

Write speeds for different disc types.

bd : int

BD-R write speed.

bd_dl : int

BD-R DL write speed.

bd_tl : int

BD-R TL write speed.

bd_xl : int

BD-R XL write speed.

cd : int

CD-R write speed.

dvd : int

DVD-R write speed.

dvd_dl : float

DVD-R DL write speed.

get_speed(disc_type: 'CD-R' | 'DVD-R' | 'DVD-R DL' | 'BD-R' | 'BD-R DL' | 'BD-R XL (100 GB)' | 'BD-R XL (128 GB)') → int | float

Get the write speed for the given disc type.

Parameters:
disc_type : _DiscType

Disc type identifier.

Returns:

Nominal write speed for disc_type.

Return type:

int | float

Raises:

ValueError – If the disc type is unknown.

gendisc.utils.get_disc_type(total: int) → 'CD-R' | 'DVD-R' | 'DVD-R DL' | 'BD-R' | 'BD-R DL' | 'BD-R XL (100 GB)' | 'BD-R XL (128 GB)'

Get disc type based on total size in bytes.

Parameters:
total : int

Total size in bytes.

Returns:

The disc type that fits total bytes.

Return type:

_DiscType

Raises:

ValueError – If the total size exceeds the maximum supported size.

Indices and tables