[multimedia/kid3] /: CI: Create fastlane metadata for Android
Urs Fleisch <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit f2661355560c36b605aa120981f86735aa5838a8 by Urs Fleisch.
Committed on 27/07/2026 at 05:20.
Pushed by ufleisch into branch 'master'.
CI: Create fastlane metadata for Android
M +3 -0 .kid3-cd.yml
A +141 -0 android/fdroid2fastlane.py
https://invent.kde.org/multimedia/kid3/-/commit/f2661355560c36b605aa120981f86735aa5838a8
diff --git a/.kid3-cd.yml b/.kid3-cd.yml
index d252ded4..d1b02546 100644
--- a/.kid3-cd.yml
+++ b/.kid3-cd.yml
@@ -106,6 +106,9 @@ kid3_macos_amd64:
ANDROID_NDK=/opt/android-sdk/ndk/23.1.7779620
ANDROID_NDK_ROOT=/opt/android-sdk/ndk/23.1.7779620
- !reference [.kid3_build_job, script]
+ - python3 kid3/android/fdroid2fastlane.py --fdroidyaml kid3/packaging/f-droid/net.sourceforge.kid3.yml --summary
+ 'Edit audio file metadata' --icon kid3/src/app/128-apps-kid3.png --screenshot
+ https://kid3.kde.org/images/ss_android_app.png --fastlanezip $CI_PROJECT_DIR/.kde-ci-packages/fastlane-kid3.zip
- python3 ci-notary-service/signapk.py -v --config $KDECI_SIGNAPK_CONFIG $CI_PROJECT_DIR/.kde-ci-packages/*.apk
- python3 ci-notary-service/publishonfdroid.py -v --config $KDECI_PUBLISHONFDROID_CONFIG --fastlane $CI_PROJECT_DIR/.kde-ci-packages/fastlane-*.zip $CI_PROJECT_DIR/.kde-ci-packages/*.apk
diff --git a/android/fdroid2fastlane.py b/android/fdroid2fastlane.py
new file mode 100755
index 00000000..a39f494c
--- /dev/null
+++ b/android/fdroid2fastlane.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+"""
+Generate fastlane metadata from F-Droid YAML file.
+
+Usage:
+ python3 fdroid2fastlane.py --fdroidyaml kid3/packaging/f-droid/net.sourceforge.kid3.yml \
+ --summary 'Edit audio file metadata' \
+ --icon kid3/src/app/128-apps-kid3.png \
+ --screenshot https://kid3.kde.org/images/ss_android_app.png \
+ --fastlanezip fastlane.zip
+"""
+
+import argparse
+import shutil
+import urllib.request
+import zipfile
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import yaml
+
+
+def parse_description(description_text: str | list[str] | None) -> str:
+ """
+ Parse the description from F-Droid YAML.
+ Handles both plain text and the |- formatted multi-line strings.
+ """
+ if description_text is None:
+ return ""
+ if isinstance(description_text, str):
+ return description_text.strip()
+ if isinstance(description_text, list):
+ return "\n".join(description_text).strip()
+ return str(description_text).strip()
+
+
+def extract_metadata(fdroid_yaml_path: str) -> dict[str, str]:
+ """
+ Extract relevant metadata from F-Droid YAML file.
+ Returns a dictionary with title, short_description, full_description.
+ """
+ with open(fdroid_yaml_path, "r", encoding="utf-8") as f:
+ data = yaml.safe_load(f)
+ description = parse_description(data.get("Description", ""))
+ short = ""
+ if description:
+ lines = [line.strip() for line in description.split("\n") if line.strip()]
+ if lines:
+ short = lines[0]
+ else:
+ description = data.get("Summary", "")
+ short = description
+ if len(short) > 80:
+ short = short[:77] + "..."
+ return {
+ "title": data.get("AutoName", ""),
+ "short_description": short,
+ "full_description": description,
+ }
+
+
+def create_fastlane_structure(
+ metadata: dict[str, str], summary="", icon_path="", screenshot_path=""
+) -> TemporaryDirectory[str]:
+ """
+ Create the fastlane directory structure in a temporary directory.
+ Returns the path to the temp directory.
+ """
+ temp_dir = TemporaryDirectory()
+ root_path = Path(temp_dir.name)
+ metadata_dir = root_path / "metadata" / "android" / "en-US"
+ metadata_dir.mkdir(parents=True, exist_ok=True)
+ title_file = metadata_dir / "title.txt"
+ title_file.write_text(metadata["title"], encoding="utf-8")
+ short_desc_file = metadata_dir / "short_description.txt"
+ short_desc_file.write_text(
+ summary if summary else metadata["short_description"], encoding="utf-8"
+ )
+ full_desc_file = metadata_dir / "full_description.txt"
+ full_desc_file.write_text(metadata["full_description"], encoding="utf-8")
+ images_dir = root_path / "metadata" / "android" / "images"
+ images_dir.mkdir(parents=True, exist_ok=True)
+ if icon_path and Path(icon_path).exists():
+ shutil.copy2(icon_path, images_dir / "icon.png")
+ if screenshot_path:
+ screenshots_dir = images_dir / "phoneScreenshots"
+ screenshots_dir.mkdir(parents=True, exist_ok=True)
+ if screenshot_path.startswith("http"):
+ try:
+ urllib.request.urlretrieve(
+ screenshot_path, screenshots_dir / "shot.png"
+ )
+ except urllib.request.HTTPError:
+ print(f"Could not download {screenshot_path}")
+ if Path(screenshot_path).exists():
+ shutil.copy2(screenshot_path, screenshots_dir)
+ return temp_dir
+
+
+def create_fastlane_zip(
+ temp_dir: TemporaryDirectory[str], output_zip_path: str
+) -> None:
+ """
+ Create a zip file from the temporary directory structure.
+ """
+ with zipfile.ZipFile(output_zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
+ root_path = Path(temp_dir.name)
+ for file_path in root_path.rglob("*"):
+ if file_path.is_file():
+ rel_path = file_path.relative_to(root_path)
+ zipf.write(file_path, str(rel_path))
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Generate fastlane metadata from F-Droid YAML file"
+ )
+ parser.add_argument("--fdroidyaml", required=True, help="Path to F-Droid YAML file")
+ parser.add_argument("--summary", help="Short description", default="")
+ parser.add_argument("--icon", help="Paths to icon file", default="")
+ parser.add_argument("--screenshot", help="Path to screenshot file", default="")
+ parser.add_argument(
+ "--fastlanezip",
+ required=True,
+ help="Path where the output fastlane zip file should be created",
+ )
+ args = parser.parse_args()
+ metadata = extract_metadata(args.fdroidyaml)
+ temp_dir = create_fastlane_structure(
+ metadata,
+ summary=args.summary,
+ icon_path=args.icon,
+ screenshot_path=args.screenshot,
+ )
+ create_fastlane_zip(temp_dir, args.fastlanezip)
+ temp_dir.cleanup()
+ print(f"Created fastlane zip archive: {args.fastlanezip}")
+
+
+if __name__ == "__main__":
+ main()