diff --git a/README.md b/README.md index 5996d5f..5881088 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ $ m4b-util cover /path/to/book.m4b --extract /path/to/old/cover.png --apply-cove ``` ### Labels -The `labels` command converts between Audacity labels, FFMPEG metadata, and Audiobook chapter metadata. Label end times -are ignored, as audiobooks need contiguous, non-overlapping chapters. When converting from a label file, the end time +The `labels` command converts between Audacity labels, cue sheets, FFMPEG metadata, and Audiobook chapter metadata. Label end times +are ignored, as audiobooks need contiguous, non-overlapping chapters. When converting from a label file, the end time of each segment is set from the start time of the next segment. **Example:** @@ -30,6 +30,11 @@ of each segment is set from the start time of the next segment. $ m4b-util labels --from-label-file /path/to/labels.txt --to-book /path/to/existing/book.m4b --to-metadata-file /path/to/new_labels.txt ``` +**Example:** +```shell +$ m4b-util labels --from-cue-file /path/to/chapters.cue --to-cue-file /path/to/new.cue +``` + **Example:** ```shell $ m4b-util bind /path/to/inputs --title "My Book" --cover /path/to/cover.png -e m4a -e .mp4 --output-dir /path/to/output diff --git a/src/m4b_util/helpers/__init__.py b/src/m4b_util/helpers/__init__.py index d221864..df1b001 100644 --- a/src/m4b_util/helpers/__init__.py +++ b/src/m4b_util/helpers/__init__.py @@ -1,3 +1,9 @@ """A Package full of helper functions.""" from .audiobook import Audiobook # noqa: F401 from .segment_data import SegmentData # noqa: F401 +from .cue import ( + cue_from_segment_data, # noqa: F401 + cue_time_to_seconds, # noqa: F401 + seconds_to_cue_time, # noqa: F401 + segment_data_from_cue, # noqa: F401 +) diff --git a/src/m4b_util/helpers/cue.py b/src/m4b_util/helpers/cue.py new file mode 100644 index 0000000..d5b5b08 --- /dev/null +++ b/src/m4b_util/helpers/cue.py @@ -0,0 +1,68 @@ +"""Cue sheet conversion utilities.""" + +from __future__ import annotations + +# Standard Library +import re +from typing import Iterable, List + +from .segment_data import SegmentData + + +def cue_time_to_seconds(cue_time: str) -> float: + """Convert cue sheet time into seconds.""" + minute, second, frame = [int(x) for x in cue_time.split(":")] + return minute * 60 + second + frame / 75 + + +def seconds_to_cue_time(seconds: float) -> str: + """Convert seconds into cue sheet time.""" + total_frames = round(seconds * 75) + minutes, remainder = divmod(total_frames, 75 * 60) + secs, frames = divmod(remainder, 75) + return f"{minutes:02d}:{secs:02d}:{frames:02d}" + + +def segment_data_from_cue(lines: Iterable[str]) -> List[SegmentData]: + """Convert cue sheet lines to a list of segments.""" + segments: List[SegmentData] = [] + previous = None + current_title = None + cue_regex = re.compile(r"^\s*INDEX\s+01\s+(?P