CI/CD Examples
Ready-to-use examples for integrating LogTalk into your release workflow. Automatically generate audio or video content whenever you publish a new release.
GitHub Actions
Trigger audio generation on release publish. This workflow extracts the release notes and creates an audio version automatically.
.github/workflows/release-audio.yml
name: Generate Release Audioon:release:types: [published]jobs:generate-audio:runs-on: ubuntu-lateststeps:- name: Generate changelog audioenv:LOGTALK_API_KEY: ${{ secrets.LOGTALK_API_KEY }}run: |# Extract release bodyCHANGELOG="${{ github.event.release.body }}"VERSION="${{ github.event.release.tag_name }}"# Create episodeRESPONSE=$(curl -s -X POST https://api.logtalk.io/v1/episodes \-H "Authorization: Bearer $LOGTALK_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: ${{ github.repository }}-$VERSION" \-d "{\"content\": $(echo "$CHANGELOG" | jq -Rs .),\"mode\": \"changelog\",\"output_format\": \"audio\",\"settings\": {\"product_name\": \"${{ github.repository }}\",\"version\": \"$VERSION\",\"tone\": \"casual\"}}")# Check for successSUCCESS=$(echo "$RESPONSE" | jq -r '.success')if [ "$SUCCESS" != "true" ]; thenecho "Error: $(echo "$RESPONSE" | jq -r '.error.message')"exit 1fi# Output audio URLAUDIO_URL=$(echo "$RESPONSE" | jq -r '.data.audio_url')echo "Audio generated: $AUDIO_URL"echo "AUDIO_URL=$AUDIO_URL" >> $GITHUB_OUTPUT- name: Update release with audio linkenv:GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}run: |gh release edit ${{ github.event.release.tag_name }} \--notes "${{ github.event.release.body }}---**Listen to this release:** [Audio version]($AUDIO_URL)"
Advanced: Video GenerationComing Soon
For video generation with async processing and webhook notification:
.github/workflows/release-video.yml
name: Generate Release Videoon:release:types: [published]jobs:generate-video:runs-on: ubuntu-lateststeps:- name: Start video generationid: createenv:LOGTALK_API_KEY: ${{ secrets.LOGTALK_API_KEY }}run: |CHANGELOG="${{ github.event.release.body }}"VERSION="${{ github.event.release.tag_name }}"RESPONSE=$(curl -s -X POST https://api.logtalk.io/v1/episodes \-H "Authorization: Bearer $LOGTALK_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: video-${{ github.repository }}-$VERSION" \-d "{\"content\": $(echo "$CHANGELOG" | jq -Rs .),\"mode\": \"changelog\",\"output_format\": \"video\",\"wait\": true,\"settings\": {\"product_name\": \"${{ github.repository }}\",\"version\": \"$VERSION\"}}")SUCCESS=$(echo "$RESPONSE" | jq -r '.success')if [ "$SUCCESS" != "true" ]; thenecho "Error: $(echo "$RESPONSE" | jq -r '.error.message')"exit 1fiVIDEO_URL=$(echo "$RESPONSE" | jq -r '.data.video_url')echo "video_url=$VIDEO_URL" >> $GITHUB_OUTPUT- name: Post video to Slackenv:SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}run: |curl -X POST $SLACK_WEBHOOK \-H "Content-Type: application/json" \-d "{\"text\": \"New release video for ${{ github.event.release.tag_name }}\",\"attachments\": [{\"title\": \"Watch Release Video\",\"title_link\": \"${{ steps.create.outputs.video_url }}\"}]}"
GitLab CI/CD
Generate audio from your CHANGELOG.md when pushing tags:
.gitlab-ci.yml
stages:- releasegenerate_changelog_audio:stage: releaseimage: curlimages/curl:latestrules:- if: $CI_COMMIT_TAGvariables:LOGTALK_API_KEY: $LOGTALK_API_KEYscript:- |# Extract changelog for this versionVERSION=$CI_COMMIT_TAG# Read CHANGELOG.md content (you may want to extract just the relevant section)CHANGELOG=$(cat CHANGELOG.md | head -100)# Generate audioRESPONSE=$(curl -s -X POST https://api.logtalk.io/v1/episodes \-H "Authorization: Bearer $LOGTALK_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: $CI_PROJECT_PATH-$VERSION" \-d "{\"content\": $(echo "$CHANGELOG" | jq -Rs .),\"output_format\": \"audio\",\"settings\": {\"product_name\": \"$CI_PROJECT_NAME\",\"version\": \"$VERSION\"}}")# Parse responseSUCCESS=$(echo "$RESPONSE" | jq -r '.success')if [ "$SUCCESS" = "true" ]; thenAUDIO_URL=$(echo "$RESPONSE" | jq -r '.data.audio_url')echo "Audio URL: $AUDIO_URL"elseecho "Error: $(echo "$RESPONSE" | jq -r '.error.message')"exit 1fi
Bash Script
A reusable bash script for converting changelogs from the command line:
logtalk-convert.sh
#!/bin/bash# logtalk-convert.sh - Convert changelog to audioset -e# ConfigurationAPI_KEY="${LOGTALK_API_KEY:?LOGTALK_API_KEY environment variable not set}"API_URL="https://api.logtalk.io/v1/episodes"# Parse argumentsCHANGELOG_FILE=""OUTPUT_FORMAT="audio"PRODUCT_NAME=""VERSION=""usage() {echo "Usage: $0 -f <changelog_file> [-o audio|video] [-p product_name] [-v version]"echo ""echo "Options:"echo " -f Path to changelog file (required)"echo " -o Output format: audio (default) or video"echo " -p Product name"echo " -v Version number"exit 1}while getopts "f:o:p:v:h" opt; docase $opt inf) CHANGELOG_FILE="$OPTARG" ;;o) OUTPUT_FORMAT="$OPTARG" ;;p) PRODUCT_NAME="$OPTARG" ;;v) VERSION="$OPTARG" ;;h) usage ;;*) usage ;;esacdoneif [ -z "$CHANGELOG_FILE" ]; thenecho "Error: Changelog file is required"usagefiif [ ! -f "$CHANGELOG_FILE" ]; thenecho "Error: File not found: $CHANGELOG_FILE"exit 1fi# Read changelog contentCONTENT=$(cat "$CHANGELOG_FILE")# Build settings JSONSETTINGS="{}"if [ -n "$PRODUCT_NAME" ]; thenSETTINGS=$(echo "$SETTINGS" | jq --arg name "$PRODUCT_NAME" '.product_name = $name')fiif [ -n "$VERSION" ]; thenSETTINGS=$(echo "$SETTINGS" | jq --arg ver "$VERSION" '.version = $ver')fi# Make API requestecho "Converting changelog to $OUTPUT_FORMAT..."RESPONSE=$(curl -s -X POST "$API_URL" \-H "Authorization: Bearer $API_KEY" \-H "Content-Type: application/json" \-d "{\"content\": $(echo "$CONTENT" | jq -Rs .),\"output_format\": \"$OUTPUT_FORMAT\",\"settings\": $SETTINGS}")# Check resultSUCCESS=$(echo "$RESPONSE" | jq -r '.success')if [ "$SUCCESS" = "true" ]; thenSTATUS=$(echo "$RESPONSE" | jq -r '.data.status')if [ "$STATUS" = "completed" ]; thenAUDIO_URL=$(echo "$RESPONSE" | jq -r '.data.audio_url')DURATION=$(echo "$RESPONSE" | jq -r '.data.duration_seconds')echo "Success!"echo "Duration: ${DURATION}s"echo "Audio URL: $AUDIO_URL"elif [ "$STATUS" = "processing" ]; thenPOLL_URL=$(echo "$RESPONSE" | jq -r '.data.poll_url')echo "Processing started. Poll for status at:"echo "$POLL_URL"fielseERROR=$(echo "$RESPONSE" | jq -r '.error.message')echo "Error: $ERROR"exit 1fi
Usage:
# Basic usage./logtalk-convert.sh -f CHANGELOG.md# With options./logtalk-convert.sh -f CHANGELOG.md -p "MyApp" -v "2.0.0"# Video output (coming soon)# ./logtalk-convert.sh -f CHANGELOG.md -p "MyApp" -v "2.0.0" -o video# Make sure to set your API keyexport LOGTALK_API_KEY="lt_live_your_key_here"
Node.js
A complete Node.js module for interacting with the LogTalk API:
logtalk.js
// logtalk.js - LogTalk API clientconst LOGTALK_API_KEY = process.env.LOGTALK_API_KEY;const BASE_URL = 'https://api.logtalk.io/v1';/*** Create a episode*/async function createConversion(options) {const {content,mode = 'changelog',outputFormat = 'audio',settings = {},webhookUrl,idempotencyKey,} = options;const headers = {'Authorization': `Bearer ${LOGTALK_API_KEY}`,'Content-Type': 'application/json',};if (idempotencyKey) {headers['Idempotency-Key'] = idempotencyKey;}const response = await fetch(`${BASE_URL}/episodes`, {method: 'POST',headers,body: JSON.stringify({content,mode,output_format: outputFormat,settings,webhook_url: webhookUrl,}),});const data = await response.json();if (!data.success) {throw new Error(`LogTalk API Error: ${data.error.message}`);}return data.data;}/*** Get episode status*/async function getConversion(episodeId) {const response = await fetch(`${BASE_URL}/episodes/${episodeId}`, {headers: {'Authorization': `Bearer ${LOGTALK_API_KEY}`,},});const data = await response.json();if (!data.success) {throw new Error(`LogTalk API Error: ${data.error.message}`);}return data.data;}/*** Wait for episode to complete (polling)*/async function waitForConversion(episodeId, maxWaitMs = 120000) {const startTime = Date.now();const pollInterval = 5000;while (Date.now() - startTime < maxWaitMs) {const episode = await getConversion(episodeId);if (episode.status === 'completed') {return episode;}if (episode.status === 'failed') {throw new Error(`Conversion failed: ${episode.error?.message}`);}await new Promise(resolve => setTimeout(resolve, pollInterval));}throw new Error('Conversion timed out');}/*** Get current usage*/async function getUsage() {const response = await fetch(`${BASE_URL}/usage`, {headers: {'Authorization': `Bearer ${LOGTALK_API_KEY}`,},});const data = await response.json();if (!data.success) {throw new Error(`LogTalk API Error: ${data.error.message}`);}return data.data;}module.exports = {createConversion,getConversion,waitForConversion,getUsage,};
Usage in your application:
const logtalk = require('./logtalk');async function generateReleaseAudio(version, changelog) {try {// Check usage firstconst usage = await logtalk.getUsage();console.log(`Audio quota: ${usage.audio.remaining}/${usage.audio.quota} remaining`);if (usage.audio.remaining === 0) {throw new Error('No audio credits remaining');}// Create episodeconst episode = await logtalk.createConversion({content: changelog,settings: {product_name: 'MyApp',version: version,tone: 'casual',},idempotencyKey: `release-${version}`,});console.log(`Audio generated: ${episode.audio_url}`);return episode;} catch (error) {console.error('Failed to generate audio:', error.message);throw error;}}// Example usagegenerateReleaseAudio('2.0.0', '## v2.0.0\n- New feature\n- Bug fix');
Python
A Python module for the LogTalk API:
logtalk.py
# logtalk.py - LogTalk API clientimport osimport timeimport requestsLOGTALK_API_KEY = os.environ.get('LOGTALK_API_KEY')BASE_URL = 'https://api.logtalk.io/v1'class LogTalkError(Exception):"""LogTalk API error"""def __init__(self, code, message):self.code = codeself.message = messagesuper().__init__(f"{code}: {message}")def _make_request(method, endpoint, **kwargs):"""Make an authenticated API request"""headers = kwargs.pop('headers', {})headers['Authorization'] = f'Bearer {LOGTALK_API_KEY}'response = requests.request(method,f'{BASE_URL}{endpoint}',headers=headers,**kwargs)data = response.json()if not data.get('success'):error = data.get('error', {})raise LogTalkError(error.get('code'), error.get('message'))return data.get('data')def create_episode(content: str,mode: str = 'changelog',output_format: str = 'audio',settings: dict = None,webhook_url: str = None,idempotency_key: str = None,):"""Create a new episode"""headers = {}if idempotency_key:headers['Idempotency-Key'] = idempotency_keypayload = {'content': content,'mode': mode,'output_format': output_format,}if settings:payload['settings'] = settingsif webhook_url:payload['webhook_url'] = webhook_urlreturn _make_request('POST', '/episodes', json=payload, headers=headers)def get_episode(episode_id: str):"""Get episode status"""return _make_request('GET', f'/episodes/{episode_id}')def wait_for_episode(episode_id: str, max_wait_seconds: int = 120):"""Poll until episode completes"""start_time = time.time()poll_interval = 5while time.time() - start_time < max_wait_seconds:episode = get_episode(episode_id)if episode['status'] == 'completed':return episodeif episode['status'] == 'failed':error = episode.get('error', {})raise LogTalkError(error.get('code', 'GENERATION_FAILED'),error.get('message', 'Conversion failed'))time.sleep(poll_interval)raise LogTalkError('TIMEOUT', 'Conversion timed out')def get_usage():"""Get current usage statistics"""return _make_request('GET', '/usage')# Example usageif __name__ == '__main__':# Check usageusage = get_usage()print(f"Audio: {usage['audio']['remaining']}/{usage['audio']['quota']} remaining")# Create episodechangelog = """## v2.0.0### Added- Dark mode support- Real-time collaboration### Fixed- Login timeout issue"""result = create_episode(content=changelog,settings={'product_name': 'MyApp','version': '2.0.0','tone': 'casual'})print(f"Audio URL: {result['audio_url']}")