{"id":42569172,"url":"https://github.com/floriankimmel/podflow","last_synced_at":"2026-01-28T21:04:13.427Z","repository":{"id":157053512,"uuid":"618291744","full_name":"floriankimmel/podflow","owner":"floriankimmel","description":"A CLI tool to automate everything related to uploading a podcast episode. ","archived":false,"fork":false,"pushed_at":"2026-01-01T20:11:41.000Z","size":8749,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-07T04:51:08.170Z","etag":null,"topics":["cli","podcast","published","publishing"],"latest_commit_sha":null,"homepage":"https://laufendentdecken-podcast.at/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/floriankimmel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-03-24T06:41:21.000Z","updated_at":"2026-01-01T20:11:45.000Z","dependencies_parsed_at":"2024-02-27T08:04:28.195Z","dependency_job_id":"3b996bb4-b588-428c-afb7-0a66b4324070","html_url":"https://github.com/floriankimmel/podflow","commit_stats":null,"previous_names":["floriankimmel/laufenentdecken-cli"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/floriankimmel/podflow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floriankimmel%2Fpodflow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floriankimmel%2Fpodflow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floriankimmel%2Fpodflow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floriankimmel%2Fpodflow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/floriankimmel","download_url":"https://codeload.github.com/floriankimmel/podflow/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floriankimmel%2Fpodflow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28851838,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T15:15:36.453Z","status":"ssl_error","status_checked_at":"2026-01-28T15:15:13.020Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","podcast","published","publishing"],"created_at":"2026-01-28T21:04:08.228Z","updated_at":"2026-01-28T21:04:13.421Z","avatar_url":"https://github.com/floriankimmel.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🎙️ Podflow\n\nA CLI tool to automate everything related to uploading a podcast episode. It is highly configurable to your specific needs.\n## 🫶 Features\n- 💡 Fully configurable - define your own workflow  \n-  🧠 Stateful. Each successful step will not be executed again.  \n-  🎖️ Choose from different services like FTP (delete, upload and download), [Amazon S3](https://aws.amazon.com/de/s3/), [Auphonic](https://auphonic.com/), [SteadyHQ](https://steadyhq.com/en/).  \n-  🔖 Create chapter marks independent of the audio recording tool.  \n## 📦 Installation\n\nFind all the releases [here](https://github.com/floriankimmel/laufenentdecken-cli/releases). Either download it directly from there or install it directly from the source using Go's install command.\n\n```bash\ngo install github.com/floriankimmel/podflow@latest\n```\n\n### 🍯 Homebrew\n\nIf you are on macOS, you can also use [Homebrew](https://brew.sh/) to install podflow.\n\n```bash\nbrew tag floriankimmel/podflow\nbrew install podflow\n```\n\n## 🎙️ Usage\n\n### 🔖 Chapter Marks\n\nDuring the recording, you can create chapter marks independent of your recording software. Each mark will be stored in the state.yml of the project.\n\n```bash\npodflow chapter start | end | add | toggle-pause\n```\n| Argument      | Description |\n| ------------- | -------------------------------\n| `start`| Mark the time when the recording has started. |\n| `add` | Add a new chapter mark |\n| `end` | Mark the time the recording has ended |\n| `toggle-pause` | Start/End a pause. This time will be subtracted when exporting the chapter marks |\n\nTo export chapter marks to a [Ultraschall](https://ultraschall.fm/), [Podlove](https://docs.podlove.org/), and [Auphonic](https://auphonic.com) compatible format, use\n\n```bash\npodflow chapter export\n```\n\nTo start publishing an episode, move to the folder containing all the necessary files and run:\n\n### 🏁 Publishing\n\n```bash\npodflow publish\n```\n\nThis will first check if all preconditions are met and afterwards execute each configured step.\n\nTo just check if all requirements are met before starting the upload run:\n\n```bash\npodflow check\n```\n\n## 📂 Open\n```bash\npodflow open\n```\n\nThe open command opens the current episode in WordPress in your browser.\n\n## 💻 State\n\n```bash\npodflow state\n```\n\nThe state command shows you the current state of the episode in a human-readable way.\n### How does it work?\n\nSometimes services are not available, and errors can happen. Therefore, Podflow is stateful and makes executing the command again and again very easy. Everything that has already happened successfully will be remembered in `{{folderName}}.state.yml`, which makes it possible for Podflow to continue where it left off.\n\n### 🧑‍💻 Wordpress\n```yml\n- wordpress:\n    wordpressID: \"1\"\n    podloveID: \"2\"\n    featuredMediaID: \"3\"\n```\n\nWe do store everything related to the WordPress article to make sure there are no unwanted side effects when rerunning the scheduled task.\n### 🧑‍💻 Metadata\n```yml\n- metadata:\n    episodeNumber: \"239\"\n    releaseDate: \"2025-01-12 09:00:00\"\n    title: Test\n```\n\n| Argument        | Description                                                                              |\n| --------------- | ---------------------------------------------------------------------------------------- |\n| `episodeNumber` | Episode number taken from the configuration and increased by 1. It can also be a string. |\n| `releaseDate`   | The actual specific datetime when this episode should be released to the public.         |\n| `title`         | Title provided by the user.                                                              |\n\n### ✅ Successfully executed steps\n\n```yml\nftpUploaded: true\nftpDeleted: true\ns3Uploaded: true\nauphonicProduction: true\nwordpressBlogCreated: true\nsteadyHqCreated: true\ndownloaded: true\n```\n\nIf present, the associated step has been executed successfully and will not be tried again.\n### 🔖 Chapter marks\n\n```yml\nchapterMarks:\n   - name: Start\n     time: 2024-02-20T13:26:29.597423+01:00\n```\n\nIf chapter marks were added during the recording, they are part of the state file ready to be exported.\n## 📦 Archive\n\nPart of the lifecycle of a podcast episode is to archive it once it is published. You don't want to delete the entire production because it might be needed in the future. So with \n\n```bash\npodflow archive \u003cFoldername\u003e\n```\n\nthe production can be moved to a configured archive folder. The necessary configuration can be found in the next section.\n## ⚙️ Configuration\n\n### Loading configuration\n\nBy default, Podflow looks for a configuration in the `$HOME\\.config\\config.yml` file. If there is a need for different configuration files, the default name `config.yml` can be overwritten by using the environment variable `PODFLOW_CONFIG_FILE`. So, by running\n\n```bash\nPODFLOW_CONFIG_FILE=test.yml podflow check\n```\n\npodflow will load `$HOME\\.config\\test.yml`.\n\n### General meta information\n\nGeneral information about the release of an episode\n\n```yml\ncurrentEpisode: 240\nreleaseDay: Friday\nreleaseTime: \"09:00:00\"\n```\n| Argument      | Description |\n| ------------- | -------------------------------\n| `currentEpisode` | Current episode number. It will be updated once a new episode has been published |\n| `releaseDay` | Day of the week: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, or Sunday |\n| `releaseTime` | Time of day (hh:mm:ss): 09:00:00 |\n\nSo episode #`currentEpisode` will be released next `releaseDay` at `releaseTime`.\n### Precondition(s)\n\nTo ensure everything is ready to start the upload workflow, certain checks can be configured.\n\n```yml\nfiles:\n    - name: File\n      fileName: 'file.m4a'\n      required: true | false\n      notEmpty: true | false\n      umlauteNotAllowed: true | false\n```\n\n| Argument      | Description |\n| ------------- | -------------------------------\n| `fileName` | Path of the file. Placeholders can be used here |\n| `required` | If set to true, uploading won't start without this file being present |\n| `notEmpty` | If set to true, uploading won't start without the file size being greater than 0 |\n| `umlauteNotAllowed` | If set to true, uploading won't start if the file name contains umlauts (ä, ü, ö) |\n\nThis configuration is used by both commands `check` and `publish`.\n\n### Placeholders\n\n| Placeholder      | Description |\n| ------------- | -------------------------------\n| `{{folderName}}` | Folder in which this script is executed |\n| `{{episodeNumber}}` | Currently configured episode number |\n| `{{env.ENV_VARIABLE}}` | Any environment variable. Preferred way to store secrets |\n\n### Steps\n\nCombination of services that make up the automated podcast workflow.\n\n```yml\nsteps:\n    - ftp: ...\n    - s3: ...\n    - auphonic: ...\n    - download: ...\n    - wordpress: ...\n    - steadyhq:: ...\n```\n\n#### FTP\n\n```yml\n    -ftp:\n        host: ftp.host.at\n        port: \"21\"\n        username: \"{{env.FTP_USER}}\"\n        password:  \"{{env.FTP_PWD}}\"\n        files:\n          - source: '{{folderName}}.m4a'\n            target: '{{episodeNumber}}_{{folderName}}.m4a'\n```\n\n| Argument      | Description |\n| ------------- | -------------------------------\n| `source` | File name on your local machine |\n| `target` | File name on the FTP server |\n\n#### Delete FTP Files\n\n```yml\n    -ftp:\n        host: ftp.host.at\n        port: \"21\"\n        username: \"{{env.FTP_USER}}\"\n        password:  \"{{env.FTP_PWD}}\"\n        delete:\n          - target: '{{episodeNumber}}_{{folderName}}.m4a'\n```\n\n| Argument      | Description |\n| ------------- | -------------------------------\n| `target` | File name on the FTP server |\n\n#### Amazon S3\n\n```yml\n    - s3:\n        buckets:\n          - region: amazon-region-id\n            name: bucket-name\n            files:\n              - source: '{{folderName}}.m4a'\n                target: '{{episodeNumber}}_{{folderName}}.m4a'\n```\n\n| Argument      | Description |\n| ------------- | -------------------------------\n| `region` | Region ID defined by Amazon: eu-central-1 |\n| `name` | Name of the S3 bucket |\n| `source` | File name on your local machine |\n| `target` | File name in the S3 bucket |\n\n#### Auphonic\n\nEnhance your audio quality with [Auphonic](https://auphonic.com/)\n\n```yml\n    - auphonic:\n        username: auphonic-username\n        password: \"{{env.AUPHONIC_PWD}}\"\n        preset: preset\n        fileServer: fileserver\n        title: \"{{episodeTitle}}\"\n        files:\n          - episode: '{{episodeNumber}}_{{folderName}}.m4a'\n            image: '{{episodeNumber}}_{{folderName}}.png'\n            chapters: '{{episodeNumber}}_{{folderName}}.chapters.txt'\n```\n\n| Argument      | Description |\n| ------------- | -------------------------------\n| `preset` | UUID of the referenced preset; you can find it on the [Preset Page](https://auphonic.com/engine/presets/) |\n| `fileServer` | URL of the server Auphonic tries to get the audio/image/chapter data from |\n| `title` | Auphonic title. The only field that allows `{{episodeTitle}}` as a placeholder |\n| `episode` | File name of the episode. If the file is not present, production will not be started |\n| `image` | File name of the episode image. |\n| `chapters` | File name of the episode chapters file. |\n\n#### Download\n\nDownload files to the local machine. The current use case is to download Auphonic output afterwards in order to upload it manually to Patreon/Steady.\n\n```yml\n    - download:\n        host: ftp.host.at\n        port: \"21\"\n        username: \"{{env.FTP_USER}}\"\n        password:  \"{{env.FTP_PWD}}\"\n        files:\n          - target: '{{episodeNumber}}_{{folderName}}.m4a'\n            source: '{{episodeNumber}}_{{folderName}}.m4a'\n```\n\n| Argument      | Description |\n| ------------- | -------------------------------\n| `target` | File name on your local machine |\n| `source` | File name in the S3 bucket |\n\n#### Wordpress\n\nSchedule WordPress blog post. [Podlove](https://podlove.org/) version 4 (or higher) is required to be installed on the WordPress site.\n\n```yml\n    - wordpress:\n        apiKey: \"{{env.WORDPRESS_API_KEY}}\"\n        server: wordpress.server.at\n        episode: '{{episodeNumber}}_{{folderName}}'\n        image: '{{folderName}}.png'\n        showNotes: '{{folderName}}.md'\n        chapter: '{{folderName}}.chapters.txt'\n        contentFile: '{{folderName}}.content.md'\n```\n\n| Argument      | Description |\n| ------------- | -------------------------------\n| `episode` | File name of the episode without extension. This is used to link your Auphonic production with Podlove |\n| `image` | Featured image of the post |\n| `showNotes` | Blog post content |\n| `chapter` | Chapters used for the Podlove web player |\n| `contentFile` | Optional content file for SEO metadata extraction. If provided, Podflow will parse this file for Focus Keywords and Meta Description |\n\n##### SEO and Summary Configuration\n\nWhen using the `contentFile` option, Podflow can automatically extract SEO metadata from your content file. The content file should include the following structured information:\n\n```markdown\n**Meta Description:**\nYour episode meta description for SEO purposes.\n\n**Focus Keywords:**\nkeyword1, keyword2, keyword3\n```\n\nPodflow will:\n- Set the Yoast SEO focus keywords (`_yoast_wpseo_focuskw`)\n- Set the meta description (`_yoast_wpseo_metadesc`)\n- Configure the episode summary field with the meta description\n\n#### SteadyHq\n\nSchedule SteadyHQ audio posts.\n\n```yml\n    - steadyhq:\n        apiKey: '{{env.STEADYHQ_API_KEY}}'\n        title: 'LEP#{{episodeNumber}} - {{episodeTitle}}'\n        image: http://{{episodeNumber}}.png\n        episode: http://{{episodeNumber}}.mp3\n        showNotes: '{{folderName}}.md'\n```\n\n| Argument      | Description |\n| ------------- | -------------------------------\n| `episode` | URL of the episode |  \n| `title` | Title of the episode |  \n| `image` | URL of the featured image |  \n| `showNotes` | File of blog post content |  \n\n### Archive\n\n```yaml\narchive:\n    target: \u003cfoldername\u003e\n```\n\nSpecify a folder where the podcast production should be archived. Currently, only folders are supported; no FTP or anything else. If you want to store it on a different server or even a NAS, you would need to mount it.\n\n### Example used by the laufendentdecken podcast\n\n```yml\ncurrentEpisode: 240\nreleaseDay: Friday\nreleaseTime: \"09:00:00\"\nfiles:\n    - name: Episode\n      fileName: '{{folderName}}.m4a'\n      required: true\n      notEmpty: false\n      umlauteNotAllowed: false\n    - name: Shownote\n      fileName: '{{folderName}}.md'\n      required: true\n      notEmpty: true\n      umlauteNotAllowed: false\n    - name: Cover\n      fileName: '{{folderName}}.png'\n      required: true\n      notEmpty: false\n      umlauteNotAllowed: false\n    - name: Chapters\n      fileName: '{{folderName}}.chapters.txt'\n      required: true\n      notEmpty: false\n      umlauteNotAllowed: false\narchive:\n    target: \u003cfolder\u003e\nsteps:\n    - ftp:\n        host: ftp.host.at\n        port: \"21\"\n        username: \"{{env.FTP_USER}}\"\n        password:  \"{{env.FTP_PWD}}\"\n        files:\n          - source: '{{folderName}}.m4a'\n            target: '{{episodeNumber}}_{{folderName}}.m4a'\n          - source: '{{folderName}}.png'\n            target: '{{episodeNumber}}_{{folderName}}.png'\n          - source: '{{folderName}}.chapters.txt'\n            target: '{{episodeNumber}}_{{folderName}}.chapters.txt'\n\n    - s3:\n        buckets:\n          - region: eu-central-1\n            name: main-bucket\n            files:\n              - source: '{{folderName}}.m4a'\n                target: '{{episodeNumber}}_{{folderName}}.m4a'\n              - source: '{{folderName}}.png'\n                target: '{{episodeNumber}}_{{folderName}}.png'\n              - source: '{{folderName}}.chapters.txt'\n                target: '{{episodeNumber}}_{{folderName}}.chapters.txt'\n          - region: eu-west-3\n            name: backup-bucket\n            files:\n              - source: '{{folderName}}.m4a'\n                target: '{{episodeNumber}}_{{folderName}}.m4a'\n              - source: '{{folderName}}.png'\n                target: '{{episodeNumber}}_{{folderName}}.png'\n              - source: '{{folderName}}.chapters.txt'\n                target: '{{episodeNumber}}_{{folderName}}.chapters.txt'\n\n    - auphonic:\n        username: \"{{env.AUPHONIC_USERNAME}}\"\n        password: \"{{env.AUPHONIC_PWD}}\"\n        preset: \u003cauphonic-preset\u003e\n        fileServer: \"http://fileserver.at/\"\n        title: \"{{episodeTitle}}\"\n        files:\n          - episode: '{{episodeNumber}}_{{folderName}}.m4a'\n            image: '{{episodeNumber}}_{{folderName}}.png'\n            chapters: '{{episodeNumber}}_{{folderName}}.chapters.txt'\n          - episode: '{{episodeNumber}}_{{folderName}}_adfree.m4a'\n            image: '{{episodeNumber}}_{{folderName}}.png'\n            chapters: '{{episodeNumber}}_{{folderName}}.chapters.txt'\n\n    - download:\n        host: ftp.host.at\n        port: \"21\"\n        username: \"{{env.FTP_USER}}\"\n        password:  \"{{env.FTP_PWD}}\"\n        files:\n          - target: '{{episodeNumber}}_{{folderName}}.m4a'\n            source: '{{episodeNumber}}_{{folderName}}.m4a'\n\n    - wordpress:\n        apiKey: \"{{env.WORDPRESS_API_KEY}}\"\n        server: \"https://your-wordpress-url.at\"\n        episode: '{{episodeNumber}}_{{folderName}}'\n        image: '{{folderName}}.png'\n        showNotes: '{{folderName}}.md'\n        chapter: '{{folderName}}.chapters.txt'\n        contentFile: '{{folderName}}.content.md'\n\n    - steadyhq:\n        apiKey: '{{env.STEADYHQ_API_KEY}}'\n        title: 'LEP#{{episodeNumber}} - {{episodeTitle}}'\n        image: http://rssfeed.laufendentdecken-podcast.at/data/{{episodeNumber}}_{{folderName}}.png\n        episode: http://rssfeed.laufendentdecken-podcast.at/data/{{episodeNumber}}_{{folderName}}.mp3\n        showNotes: '{{folderName}}.md'\n\n    - ftp:\n        host: rssfeed.laufendentdecken-podcast.at\n        port: \"21\"\n        username: '{{env.FTP_USER}}'\n        password: '{{env.FTP_PWD}}'\n        delete:\n            - target: '{{episodeNumber}}_{{folderName}}.chapters.txt'\n\n\n```\n# ✍️ Author\nFlorian Kimmel [florian@le-podcast.at](mailto:florian@le-podcast.at)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloriankimmel%2Fpodflow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffloriankimmel%2Fpodflow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloriankimmel%2Fpodflow/lists"}