VideoTools/docs/VIDEO_METADATA_GUIDE.md
Stu Leak 5b8fc452af Add FPS counter, queue improvements, Compare fixes, and comprehensive documentation
Features:
- FPS counter in conversion status showing real-time encoding speed
- Job queue now displays FPS, encoding speed (e.g., "1.2x"), and ETA for running conversions
- Copy Comparison button exports side-by-side metadata comparison report
- Auto-compare checkbox in Convert module - automatically loads Compare view after conversion
- Convert Now properly adds job to queue and displays in Job Queue with live stats
- Module badge colors in job queue now match main menu tile colors
- Fixed fullscreen compare window sizing (reduced player dimensions to prevent overflow)

Bug Fixes:
- Fixed queue state management - only one job runs at a time (prevents multiple jobs showing "running")
- Fixed Compare module slot assignment - single video drops now fill empty slot instead of overwriting
- Fixed job queue scroll rubber banding (no longer jumps back to top)
- Enhanced crop detection validation for WMV/AVI formats with dimension clamping and bounds checking

Documentation:
- VT_Player integration notes with API requirements for keyframing and trim features
- LosslessCut feature analysis for Trim module inspiration
- Video metadata guide covering MP4/MKV custom fields and NFO generation
- Trim module design specification
- Compare fullscreen mode documentation
- Updated VIDEO_PLAYER_FORK.md to mark fork as completed

Technical Changes:
- Added state tracking for FPS, speed, and ETA (main.go:197-199)
- Enhanced queue processJobs() to check for running jobs before starting new ones
- Improved Compare module drag-and-drop logic with smart slot assignment (both code paths)
- Added deferred scroll position restoration to prevent UI jumping
- Job queue Config map now carries conversion stats for display

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 07:35:48 -05:00

17 KiB

Video Metadata Guide for VideoTools

Overview

This guide covers adding custom metadata fields to video files, NFO generation, and integration with VideoTools modules.


📦 Container Format Metadata Capabilities

MP4 / MOV (MPEG-4)

Metadata storage: Atoms in moov container

Standard iTunes-compatible tags:

©nam - Title
©ART - Artist
©alb - Album
©day - Year
©gen - Genre
©cmt - Comment
desc - Description
©too - Encoding tool
©enc - Encoded by
cprt - Copyright

Custom tags (with proper keys):

----:com.apple.iTunes:DIRECTOR    - Director
----:com.apple.iTunes:PERFORMERS  - Performers
----:com.apple.iTunes:STUDIO      - Studio/Production
----:com.apple.iTunes:SERIES      - Series name
----:com.apple.iTunes:SCENE       - Scene number
----:com.apple.iTunes:CATEGORIES  - Categories/Tags

Setting metadata with FFmpeg:

ffmpeg -i input.mp4 -c copy \
  -metadata title="Scene Title" \
  -metadata artist="Performer Name" \
  -metadata album="Series Name" \
  -metadata date="2025" \
  -metadata genre="Category" \
  -metadata comment="Scene description" \
  -metadata description="Full scene info" \
  output.mp4

Custom fields:

ffmpeg -i input.mp4 -c copy \
  -metadata:s:v:0 custom_field="Custom Value" \
  output.mp4

MKV (Matroska)

Metadata storage: Tags element (XML-based)

Built-in tag support:

<Tags>
  <Tag>
    <Simple>
      <Name>TITLE</Name>
      <String>Scene Title</String>
    </Simple>
    <Simple>
      <Name>ARTIST</Name>
      <String>Performer Name</String>
    </Simple>
    <Simple>
      <Name>DIRECTOR</Name>
      <String>Director Name</String>
    </Simple>
    <Simple>
      <Name>STUDIO</Name>
      <String>Production Studio</String>
    </Simple>
    <!-- Arbitrary custom tags -->
    <Simple>
      <Name>PERFORMERS</Name>
      <String>Performer 1, Performer 2</String>
    </Simple>
    <Simple>
      <Name>SCENE_NUMBER</Name>
      <String>EP042</String>
    </Simple>
    <Simple>
      <Name>CATEGORIES</Name>
      <String>Cat1, Cat2, Cat3</String>
    </Simple>
  </Tag>
</Tags>

Setting metadata with FFmpeg:

ffmpeg -i input.mkv -c copy \
  -metadata title="Scene Title" \
  -metadata artist="Performer Name" \
  -metadata director="Director" \
  -metadata studio="Studio Name" \
  output.mkv

Advantages of MKV:

  • Unlimited custom tags (any key-value pairs)
  • Can attach files (NFO, images, scripts)
  • Hierarchical metadata structure
  • Best for archival/preservation

MOV (QuickTime)

Same as MP4 (both use MPEG-4 structure), but QuickTime supports additional proprietary tags.


📄 NFO File Format

NFO (Info) files are plain text/XML files that contain detailed metadata. Common in media libraries (Kodi, Plex, etc.).

NFO Format for Movies:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<movie>
  <title>Scene Title</title>
  <originaltitle>Original Title</originaltitle>
  <sorttitle>Sort Title</sorttitle>
  <year>2025</year>
  <releasedate>2025-12-04</releasedate>
  <plot>Scene description and plot summary</plot>
  <runtime>45</runtime> <!-- minutes -->
  <studio>Production Studio</studio>
  <director>Director Name</director>

  <actor>
    <name>Performer 1</name>
    <role>Role 1</role>
    <thumb>path/to/performer1.jpg</thumb>
  </actor>
  <actor>
    <name>Performer 2</name>
    <role>Role 2</role>
  </actor>

  <genre>Category 1</genre>
  <genre>Category 2</genre>

  <tag>Tag1</tag>
  <tag>Tag2</tag>

  <rating>8.5</rating>
  <userrating>9.0</userrating>

  <fileinfo>
    <streamdetails>
      <video>
        <codec>h264</codec>
        <width>1920</width>
        <height>1080</height>
        <durationinseconds>2700</durationinseconds>
        <aspect>1.777778</aspect>
      </video>
      <audio>
        <codec>aac</codec>
        <channels>2</channels>
      </audio>
    </streamdetails>
  </fileinfo>

  <!-- Custom fields -->
  <series>Series Name</series>
  <episode>42</episode>
  <scene_number>EP042</scene_number>
</movie>

NFO Format for TV Episodes:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<episodedetails>
  <title>Episode Title</title>
  <showtitle>Series Name</showtitle>
  <season>1</season>
  <episode>5</episode>
  <aired>2025-12-04</aired>
  <plot>Episode description</plot>
  <runtime>30</runtime>
  <director>Director Name</director>

  <actor>
    <name>Performer 1</name>
    <role>Character</role>
  </actor>

  <studio>Production Studio</studio>
  <rating>8.0</rating>
</episodedetails>

🛠️ VideoTools Integration Plan

Module: Metadata Editor (New Module)

Purpose: Edit video metadata and generate NFO files

Features:

  1. Load video → Extract existing metadata
  2. Edit fields → Standard + custom fields
  3. NFO generation → Auto-generate from metadata
  4. Embed metadata → Write back to video file (lossless remux)
  5. Batch metadata → Apply same metadata to multiple files
  6. Templates → Save/load metadata templates

UI Layout:

┌─────────────────────────────────────────────────┐
│ < METADATA                                       │ ← Purple header
├─────────────────────────────────────────────────┤
│                                                  │
│  File: scene_042.mp4                            │
│                                                  │
│  ┌─ Basic Info ──────────────────────────────┐  │
│  │ Title:       [________________]           │  │
│  │ Studio:      [________________]           │  │
│  │ Series:      [________________]           │  │
│  │ Scene #:     [____]                       │  │
│  │ Date:        [2025-12-04]                 │  │
│  │ Duration:    45:23 (auto)                 │  │
│  └──────────────────────────────────────────────┘  │
│                                                  │
│  ┌─ Performers ────────────────────────────────┐  │
│  │ Performer 1: [________________] [X]        │  │
│  │ Performer 2: [________________] [X]        │  │
│  │                           [+ Add Performer] │  │
│  └──────────────────────────────────────────────┘  │
│                                                  │
│  ┌─ Categories/Tags ──────────────────────────┐  │
│  │ [Tag1] [Tag2] [Tag3]            [+ Add]    │  │
│  └──────────────────────────────────────────────┘  │
│                                                  │
│  ┌─ Description ────────────────────────────────┐  │
│  │ [Multiline text area for plot/description] │  │
│  │                                              │  │
│  └──────────────────────────────────────────────┘  │
│                                                  │
│  ┌─ Custom Fields ────────────────────────────┐  │
│  │ Director:  [________________]              │  │
│  │ IMDB ID:   [________________]              │  │
│  │ Custom 1:  [________________]              │  │
│  │                             [+ Add Field]   │  │
│  └──────────────────────────────────────────────┘  │
│                                                  │
│  [Generate NFO] [Embed in Video] [Save Template]│
│                                                  │
└─────────────────────────────────────────────────┘

🔧 Implementation Details

1. Reading Metadata

Using FFprobe:

ffprobe -v quiet -print_format json -show_format input.mp4

# Output includes:
{
  "format": {
    "filename": "input.mp4",
    "tags": {
      "title": "Scene Title",
      "artist": "Performer Name",
      "album": "Series Name",
      "date": "2025",
      "genre": "Category",
      "comment": "Description"
    }
  }
}

Go implementation:

type VideoMetadata struct {
    Title       string
    Studio      string
    Series      string
    SceneNumber string
    Date        string
    Performers  []string
    Director    string
    Categories  []string
    Description string
    CustomFields map[string]string
}

func probeMetadata(path string) (*VideoMetadata, error) {
    cmd := exec.Command("ffprobe",
        "-v", "quiet",
        "-print_format", "json",
        "-show_format",
        path,
    )

    output, err := cmd.Output()
    if err != nil {
        return nil, err
    }

    var result struct {
        Format struct {
            Tags map[string]string `json:"tags"`
        } `json:"format"`
    }

    json.Unmarshal(output, &result)

    metadata := &VideoMetadata{
        Title:       result.Format.Tags["title"],
        Studio:      result.Format.Tags["studio"],
        Series:      result.Format.Tags["album"],
        Date:        result.Format.Tags["date"],
        Categories:  strings.Split(result.Format.Tags["genre"], ", "),
        Description: result.Format.Tags["comment"],
        CustomFields: make(map[string]string),
    }

    return metadata, nil
}

2. Writing Metadata

Using FFmpeg (lossless remux):

func embedMetadata(inputPath string, metadata *VideoMetadata, outputPath string) error {
    args := []string{
        "-i", inputPath,
        "-c", "copy", // Lossless copy
    }

    // Add standard tags
    if metadata.Title != "" {
        args = append(args, "-metadata", fmt.Sprintf("title=%s", metadata.Title))
    }
    if metadata.Studio != "" {
        args = append(args, "-metadata", fmt.Sprintf("studio=%s", metadata.Studio))
    }
    if metadata.Series != "" {
        args = append(args, "-metadata", fmt.Sprintf("album=%s", metadata.Series))
    }
    if metadata.Date != "" {
        args = append(args, "-metadata", fmt.Sprintf("date=%s", metadata.Date))
    }
    if len(metadata.Categories) > 0 {
        args = append(args, "-metadata", fmt.Sprintf("genre=%s", strings.Join(metadata.Categories, ", ")))
    }
    if metadata.Description != "" {
        args = append(args, "-metadata", fmt.Sprintf("comment=%s", metadata.Description))
    }

    // Add custom fields
    for key, value := range metadata.CustomFields {
        args = append(args, "-metadata", fmt.Sprintf("%s=%s", key, value))
    }

    args = append(args, outputPath)

    cmd := exec.Command("ffmpeg", args...)
    return cmd.Run()
}

3. Generating NFO

func generateNFO(metadata *VideoMetadata, videoPath string) (string, error) {
    nfo := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<movie>
  <title>` + escapeXML(metadata.Title) + `</title>
  <studio>` + escapeXML(metadata.Studio) + `</studio>
  <series>` + escapeXML(metadata.Series) + `</series>
  <year>` + metadata.Date + `</year>
  <plot>` + escapeXML(metadata.Description) + `</plot>
`

    // Add performers
    for _, performer := range metadata.Performers {
        nfo += `  <actor>
    <name>` + escapeXML(performer) + `</name>
  </actor>
`
    }

    // Add categories/genres
    for _, category := range metadata.Categories {
        nfo += `  <genre>` + escapeXML(category) + `</genre>
`
    }

    // Add custom fields
    for key, value := range metadata.CustomFields {
        nfo += `  <` + key + `>` + escapeXML(value) + `</` + key + `>
`
    }

    nfo += `</movie>`

    // Save to file (same name as video + .nfo extension)
    nfoPath := strings.TrimSuffix(videoPath, filepath.Ext(videoPath)) + ".nfo"
    return nfoPath, os.WriteFile(nfoPath, []byte(nfo), 0644)
}

func escapeXML(s string) string {
    s = strings.ReplaceAll(s, "&", "&amp;")
    s = strings.ReplaceAll(s, "<", "&lt;")
    s = strings.ReplaceAll(s, ">", "&gt;")
    s = strings.ReplaceAll(s, "\"", "&quot;")
    s = strings.ReplaceAll(s, "'", "&apos;")
    return s
}

4. Attaching NFO to MKV

MKV supports embedded attachments (like NFO files):

# Attach NFO file to MKV
mkvpropedit video.mkv --add-attachment scene_info.nfo --attachment-mime-type text/plain --attachment-name "scene_info.nfo"

# Or with FFmpeg (re-mux required)
ffmpeg -i input.mkv -i scene_info.nfo -c copy \
  -attach scene_info.nfo -metadata:s:t:0 mimetype=text/plain \
  output.mkv

Go implementation:

func attachNFOtoMKV(mkvPath string, nfoPath string) error {
    cmd := exec.Command("mkvpropedit", mkvPath,
        "--add-attachment", nfoPath,
        "--attachment-mime-type", "text/plain",
        "--attachment-name", filepath.Base(nfoPath),
    )
    return cmd.Run()
}

📋 Metadata Templates

Allow users to save metadata templates for batch processing.

Template JSON:

{
  "name": "Studio XYZ Default Template",
  "fields": {
    "studio": "Studio XYZ",
    "series": "Series Name",
    "categories": ["Category1", "Category2"],
    "custom_fields": {
      "director": "John Doe",
      "producer": "Jane Smith"
    }
  }
}

Usage:

  1. User creates template with common studio/series info
  2. Load template when editing new video
  3. Only fill in unique fields (title, performers, date, scene #)
  4. Batch apply template to multiple files

🎯 Use Cases

1. Adult Content Library

Title: "Scene Title"
Studio: "Production Studio"
Series: "Series Name - Season 2"
Scene Number: "EP042"
Performers: ["Performer A", "Performer B"]
Director: "Director Name"
Categories: ["Category1", "Category2", "Category3"]
Date: "2025-12-04"
Description: "Full scene description and plot"

2. Personal Video Archive

Title: "Birthday Party 2025"
Event: "John's 30th Birthday"
Location: "Los Angeles, CA"
People: ["John", "Sarah", "Mike", "Emily"]
Date: "2025-06-15"
Description: "John's surprise birthday party"

3. Movie Collection

Title: "Movie Title"
Original Title: "原題"
Director: "Christopher Nolan"
Year: "2024"
IMDB ID: "tt1234567"
Actors: ["Actor 1", "Actor 2"]
Genre: ["Sci-Fi", "Thriller"]
Rating: "8.5/10"

🔌 Integration with Existing Modules

Convert Module

  • Checkbox: "Preserve metadata" (default: on)
  • Checkbox: "Copy metadata from source" (default: on)
  • Allow adding/editing metadata before conversion

Inspect Module

  • Add tab: "Metadata" to view/edit metadata
  • Show both standard and custom fields
  • Quick edit without re-encoding

Compare Module

  • Add: "Compare Metadata" button
  • Show metadata diff between two files
  • Highlight differences

🚀 Implementation Roadmap

Phase 1: Basic Metadata Support (Week 1)

  • Read metadata with ffprobe
  • Display in Inspect module
  • Edit basic fields (title, artist, date, comment)
  • Write metadata with FFmpeg (lossless)

Phase 2: NFO Generation (Week 2)

  • NFO file generation
  • Save alongside video file
  • Load NFO and populate fields
  • Template system

Phase 3: Advanced Metadata (Week 3)

  • Custom fields support
  • Performers list
  • Categories/tags
  • Metadata Editor module UI

Phase 4: Batch & Templates (Week 4)

  • Metadata templates
  • Batch apply to multiple files
  • MKV attachment support (embed NFO)

📚 References

FFmpeg Metadata Documentation

NFO Format Standards

Matroska Tags


Summary

Yes, you can absolutely store custom metadata in video files!

Best format for rich metadata: MKV (unlimited custom tags + file attachments)

Most compatible: MP4/MOV (iTunes tags work in QuickTime, VLC, etc.)

Recommended approach for VideoTools:

  1. Support both embedded metadata (in video file) AND sidecar NFO files
  2. MKV: Embed NFO as attachment + metadata tags
  3. MP4: Metadata tags + separate .nfo file
  4. Allow users to choose what metadata to embed
  5. Generate NFO for media center compatibility (Kodi, Plex, Jellyfin)

Next steps:

  1. Add basic metadata reading to probeVideo() function
  2. Add metadata display to Inspect module
  3. Create Metadata Editor module
  4. Implement NFO generation
  5. Add metadata templates

This would be a killer feature for VideoTools! 🚀