Aiwg export

Export media collection to platform-specific formats (Plex, Jellyfin, MPD, mobile, archival)

install
source · Clone the upstream repo
git clone https://github.com/jmagly/aiwg
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jmagly/aiwg "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.agents/skills/export" ~/.claude/skills/jmagly-aiwg-export && rm -rf "$T"
manifest: .agents/skills/export/SKILL.md
source content

Export Media Collection

Export a curated media collection to platform-specific formats with appropriate transcoding, metadata, and directory structures.

Purpose

Users consume media across diverse platforms, each with different format requirements, metadata standards, and organizational conventions. This command transforms a curated collection into the optimal structure for each target platform.

Platform Profiles

Plex Profile

Formats:

  • Audio: FLAC (preferred), MP3 (320kbps), M4A (AAC)
  • Video: MP4 (H.264/H.265), MKV
  • Artwork: Embedded +
    folder.jpg
    in album directory

Directory Structure:

Music/
├── Artist Name/
│   ├── Album Name (Year)/
│   │   ├── folder.jpg
│   │   ├── 01 - Track Title.flac
│   │   ├── 02 - Track Title.flac
│   │   └── ...
│   └── Videos/
│       └── Video Title.mp4

YAML Spec:

profile: plex
audio_formats: [flac, mp3, m4a]
video_formats: [mp4, mkv]
artwork:
  embedded: true
  folder_jpg: true
  max_resolution: 1500x1500
directory_pattern: "{artist}/{album} ({year})/{track:02d} - {title}.{ext}"
video_directory: "{artist}/Videos/{title}.{ext}"
metadata:
  prefer_embedded: true
  id3v2_version: 2.4

Jellyfin Profile

Formats:

  • Audio: FLAC (preferred), Opus, MP3
  • Video: MP4, MKV, WebM
  • Artwork: Embedded +
    folder.jpg
    +
    artist.jpg
  • NFO: Supported for rich metadata

Directory Structure:

Music/
├── Artist Name/
│   ├── artist.jpg
│   ├── artist.nfo
│   ├── Album Name (Year)/
│   │   ├── folder.jpg
│   │   ├── album.nfo
│   │   ├── 01 - Track Title.flac
│   │   └── ...
│   └── Videos/
│       ├── Video Title.mp4
│       └── Video Title.nfo

YAML Spec:

profile: jellyfin
audio_formats: [flac, opus, mp3]
video_formats: [mp4, mkv, webm]
artwork:
  embedded: true
  folder_jpg: true
  artist_jpg: true
  max_resolution: 1500x1500
nfo_files:
  album: true
  artist: true
  video: true
directory_pattern: "{artist}/{album} ({year})/{track:02d} - {title}.{ext}"
video_directory: "{artist}/Videos/{title}.{ext}"
metadata:
  prefer_embedded: true
  id3v2_version: 2.4

MPD Profile

Formats:

  • Audio: FLAC (preferred), Opus, MP3
  • Artwork:
    folder.jpg
    in album directory

Directory Structure:

Music/
├── Artist Name/
│   └── Album Name/
│       ├── folder.jpg
│       ├── 01 - Track Title.flac
│       └── ...

YAML Spec:

profile: mpd
audio_formats: [flac, opus, mp3]
artwork:
  embedded: true
  folder_jpg: true
  max_resolution: 1000x1000
directory_pattern: "{artist}/{album}/{track:02d} - {title}.{ext}"
metadata:
  prefer_embedded: true
  id3v2_version: 2.4
scanning:
  auto_rescan: true
  watch_mode: inotify

Mobile Profile

Formats:

  • Audio: Opus (preferred, 128-192kbps), M4A (AAC), MP3
  • Video: MP4 (H.264, max 720p)
  • Artwork: Embedded, max 500x500

Size Constraints:

  • Transcode high-res audio to save space
  • Limit video resolution
  • Budget-aware export (stop when size limit reached)

YAML Spec:

profile: mobile
audio_formats: [opus, m4a, mp3]
audio_quality:
  opus: 128k
  m4a: 192k
  mp3: 192k
video_formats: [mp4]
video_quality:
  max_resolution: 720p
  codec: h264
  bitrate: 1500k
artwork:
  embedded: true
  max_resolution: 500x500
transcode:
  always: true
  source_formats: [flac, wav, alac]
size_budget:
  enabled: true
  prioritize_by: importance
directory_pattern: "{artist}/{album}/{track:02d} - {title}.{ext}"

Archival Profile

Formats:

  • Audio: FLAC, WAV (no lossy formats)
  • Video: MKV (preserve original quality)
  • Checksums: SHA256SUMS
  • Provenance: PROVENANCE.jsonld

Directory Structure:

Archive/
├── Artist Name/
│   ├── Album Name (Year)/
│   │   ├── 01 - Track Title.flac
│   │   ├── ...
│   │   ├── SHA256SUMS
│   │   ├── PROVENANCE.jsonld
│   │   └── folder.jpg
│   └── MANIFEST.json

YAML Spec:

profile: archival
audio_formats: [flac, wav]
video_formats: [mkv]
preserve:
  original_filenames: true
  source_metadata: true
  acquisition_dates: true
checksums:
  algorithm: sha256
  filename: SHA256SUMS
provenance:
  format: jsonld
  filename: PROVENANCE.jsonld
  include_source_urls: true
  include_timestamps: true
manifest:
  per_album: false
  per_artist: true
  format: json
directory_pattern: "{artist}/{album} ({year})/{original_filename}"

Transcoding Commands

Audio Transcoding

FLAC to Opus:

ffmpeg -i input.flac -c:a libopus -b:a 128k -vn output.opus

FLAC to MP3:

ffmpeg -i input.flac -c:a libmp3lame -b:a 320k -id3v2_version 4 output.mp3

Opus to MP3:

ffmpeg -i input.opus -c:a libmp3lame -b:a 192k -id3v2_version 4 output.mp3

Preserve Metadata:

ffmpeg -i input.flac -c:a libopus -b:a 128k -vn -map_metadata 0 output.opus

Video Transcoding

4K to 1080p:

ffmpeg -i input.mkv -vf scale=1920:1080 -c:v libx264 -crf 23 -c:a copy output.mp4

1080p to 720p (mobile):

ffmpeg -i input.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 -b:v 1500k -c:a aac -b:a 128k output.mp4

Extract Audio Only:

ffmpeg -i video.mp4 -vn -c:a libopus -b:a 192k audio.opus

Artwork Processing

Resize Artwork:

ffmpeg -i folder.jpg -vf scale=500:500:force_original_aspect_ratio=decrease -q:v 2 folder_mobile.jpg

Extract Artwork from Audio:

ffmpeg -i track.flac -an -vcodec copy folder.jpg

Embed Artwork into Audio:

ffmpeg -i track.flac -i folder.jpg -map 0:a -map 1:v -c:a copy -id3v2_version 4 -metadata:s:v title="Album cover" -metadata:s:v comment="Cover (front)" output.flac

NFO File Generation

Album NFO Template (Jellyfin/Kodi)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<album>
  <title>{album_title}</title>
  <artist>{artist_name}</artist>
  <year>{release_year}</year>
  <genre>{genre}</genre>
  <style>{style}</style>
  <mood>{mood}</mood>
  <label>{label}</label>
  <releasedate>{release_date}</releasedate>
  <rating>{rating}</rating>
  <userrating>{user_rating}</userrating>
  <review>{review}</review>
  <musicbrainzalbumid>{mbid}</musicbrainzalbumid>
  <thumb aspect="thumb">{artwork_url}</thumb>
</album>

Size-Budgeted Export (Mobile)

When exporting to mobile devices with limited storage:

  1. Calculate Space Budget:

    • Parse
      --max-size
      parameter
    • Convert to bytes
    • Reserve 10% buffer for filesystem overhead
  2. Prioritize Content:

    • Sort by importance score (frequency * rating * recency)
    • Essential albums first
    • Deep cuts last
  3. Transcode to Mobile Formats:

    • FLAC → Opus 128kbps
    • High-res video → 720p H.264
    • Resize artwork to 500x500
  4. Track Running Total:

    • Sum file sizes as files are added
    • Stop when budget exhausted
    • Generate report of included/excluded content
  5. Export Algorithm:

budget_bytes=$(( max_size_gb * 1024 * 1024 * 1024 * 9 / 10 ))
total_bytes=0

while read -r file importance; do
  file_size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file")

  if (( total_bytes + file_size > budget_bytes )); then
    echo "Budget exhausted. Stopping export." >&2
    break
  fi

  # Transcode and copy
  transcode_and_copy "$file" "$output_dir"
  total_bytes=$(( total_bytes + file_size ))
done < <(prioritized_file_list)

Archival Export

Preserve Original Formats

Never transcode. Copy files as-is to preserve quality and authenticity.

Generate SHA-256 Checksums

cd "$album_dir"
find . -type f -not -name "SHA256SUMS" -exec sha256sum {} \; > SHA256SUMS

Record Provenance

Create

PROVENANCE.jsonld
per W3C PROV standard:

{
  "@context": "https://www.w3.org/ns/prov",
  "entity": "urn:aiwg:media:album:{mbid}",
  "wasGeneratedBy": {
    "activity": "urn:aiwg:activity:acquisition:{timestamp}",
    "time": "{iso8601_timestamp}",
    "wasAssociatedWith": "urn:aiwg:agent:media-curator"
  },
  "wasDerivedFrom": [
    {
      "entity": "{source_url}",
      "type": "download",
      "time": "{acquisition_date}"
    }
  ],
  "atLocation": "{file_path}",
  "hadPrimarySource": "{authoritative_source}"
}

Generate Manifest

Per-artist

MANIFEST.json
listing all albums, tracks, formats, and checksums:

{
  "artist": "Artist Name",
  "albums": [
    {
      "title": "Album Name",
      "year": 1973,
      "tracks": [
        {
          "number": 1,
          "title": "Track Title",
          "file": "01 - Track Title.flac",
          "format": "flac",
          "checksum": "sha256:abc123..."
        }
      ]
    }
  ],
  "export_date": "{iso8601_timestamp}",
  "profile": "archival"
}

Workflow

1. Select Profile

Parse

--profile
argument. Load corresponding YAML spec.

2. Validate Source Collection

  • Scan collection directory
  • Verify files exist and are readable
  • Check format compatibility with target profile

3. Create Output Directory Structure

Based on profile's

directory_pattern
:

mkdir -p "$output_dir/{artist}/{album}"

4. Transcode If Needed

Check if source format matches target profile. If not, transcode:

if [[ "$source_ext" == "flac" ]] && [[ "$target_profile" == "mobile" ]]; then
  ffmpeg -i "$source" -c:a libopus -b:a 128k -map_metadata 0 "$target"
fi

5. Copy/Move Files

cp -a "$source" "$destination"

Or move if

--move
flag set.

6. Embed Metadata

Ensure all required metadata is embedded per profile spec.

7. Generate Supplementary Files

  • NFO files (Jellyfin profile)
  • SHA256SUMS (archival profile)
  • PROVENANCE.jsonld (archival profile)
  • folder.jpg, artist.jpg (Plex/Jellyfin profiles)

8. Report

Export complete: Plex profile
Source: /media/source/Artist Name
Destination: /media/plex/Music/Artist Name
Files exported: 142
Total size: 3.2 GB
Transcoded: 0
Copied: 142

Examples

Export to Plex

/export --profile plex ~/Music/Archive/Pink\ Floyd /media/plex/Music

Export to Mobile with Size Limit

/export --profile mobile ~/Music/Archive /sdcard/Music --max-size 32 --transcode

Archival Export

/export --profile archival ~/Music/Curated /backup/Music/Archive

Error Handling

  • Missing source files: Report and skip
  • Insufficient disk space: Abort with clear message
  • Transcode failures: Log error, optionally skip or abort
  • Permission denied: Report file path and required permissions

Performance Considerations

  • Parallel transcoding: Use GNU parallel or xargs -P for multi-core systems
  • Progress reporting: Update every N files or every M seconds
  • Disk I/O: Batch operations to minimize seeks
  • Memory: Stream large files instead of loading into memory

References

  • @$AIWG_ROOT/agentic/code/addons/aiwg-utils/rules/human-authorization.md — Seek explicit authorization before overwriting existing export targets
  • @$AIWG_ROOT/agentic/code/frameworks/media-curator/skills/curate/SKILL.md — Orchestration skill that invokes export as the final curation phase
  • @$AIWG_ROOT/agentic/code/frameworks/media-curator/skills/assemble/SKILL.md — Assembly skill used before export to prepare media collections
  • @$AIWG_ROOT/agentic/code/frameworks/media-curator/skills/integrity-verification/SKILL.md — Verify exported files match source checksums