smart_video_info 1.2.0 copy "smart_video_info: ^1.2.0" to clipboard
smart_video_info: ^1.2.0 copied to clipboard

Ultra-fast video metadata extraction powered by Smart FFmpeg Engine. No CLI, no process spawning - direct native FFmpeg API.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import 'package:smart_video_info/smart_video_info.dart';
// ignore: avoid_web_libraries_in_flutter, deprecated_member_use
import 'dart:html' as html show Url, Blob;
// ignore: unused_import
import 'package:flutter/foundation.dart' show kIsWeb;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Smart Video Info Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const VideoInfoPage(),
    );
  }
}

class VideoInfoPage extends StatefulWidget {
  const VideoInfoPage({super.key});

  @override
  State<VideoInfoPage> createState() => _VideoInfoPageState();
}

class _VideoInfoPageState extends State<VideoInfoPage> {
  SmartVideoInfo? _videoInfo;
  String? _selectedPath;
  bool _isLoading = false;
  String? _error;
  int? _extractionTimeMs;

  Future<void> _pickVideo() async {
    try {
      final result = await FilePicker.platform.pickFiles(
        type: FileType.video,
        allowMultiple: false,
      );

      if (result != null && result.files.isNotEmpty) {
        final file = result.files.first;
        
        // On web, use bytes to create blob URL
        if (kIsWeb) {
          final bytes = file.bytes;
          if (bytes != null) {
            // Create blob URL from bytes with proper MIME type
            final mimeType = _getMimeType(file.name);
            debugPrint('File: ${file.name}, MIME type: $mimeType');
            final blob = html.Blob([bytes], mimeType);
            final url = html.Url.createObjectUrlFromBlob(blob);
            debugPrint('Created blob URL: $url');
            await _getVideoInfo(url);
          } else {
            setState(() {
              _error = 'Failed to read file bytes on web platform';
            });
          }
        } else {
          // On native platforms, use path
          final path = file.path;
          if (path != null) {
            await _getVideoInfo(path);
          } else {
            setState(() {
              _error = 'Failed to get file path';
            });
          }
        }
      }
    } catch (e) {
      debugPrint('Error picking file: $e');
      setState(() {
        _error = 'Error picking file: $e';
      });
    }
  }

  String _getMimeType(String fileName) {
    final extension = fileName.toLowerCase().split('.').last;
    switch (extension) {
      case 'mp4':
      case 'm4v':
        return 'video/mp4';
      case 'webm':
        return 'video/webm';
      case 'ogv':
      case 'ogg':
        return 'video/ogg';
      case 'mov':
        return 'video/quicktime';
      case 'avi':
        return 'video/x-msvideo';
      case 'mkv':
        return 'video/x-matroska';
      case 'flv':
        return 'video/x-flv';
      case '3gp':
        return 'video/3gpp';
      case 'wmv':
        return 'video/x-ms-wmv';
      default:
        return 'video/mp4'; // Default fallback
    }
  }

  Future<void> _getVideoInfo(String path) async {
    setState(() {
      _isLoading = true;
      _error = null;
      _selectedPath = path;
      _videoInfo = null;
      _extractionTimeMs = null;
    });

    final stopwatch = Stopwatch()..start();

    try {
      debugPrint('Getting video info for: $path');
      final info = await SmartVideoInfoPlugin.getInfo(path);
      stopwatch.stop();

      debugPrint('Successfully extracted metadata in ${stopwatch.elapsedMilliseconds}ms');
      debugPrint('Video dimensions: ${info.width}x${info.height}');
      debugPrint('Duration: ${info.duration.inMilliseconds}ms');
      debugPrint('Has audio: ${info.hasAudio}');
      if (info.hasAudio) {
        debugPrint('Audio codec: ${info.audioCodec}');
        debugPrint('Sample rate: ${info.sampleRate}');
        debugPrint('Channels: ${info.channels}');
      }
      debugPrint('Stream count: ${info.streamCount}');
      
      setState(() {
        _videoInfo = info;
        _extractionTimeMs = stopwatch.elapsedMilliseconds;
        _isLoading = false;
      });
    } on SmartVideoInfoException catch (e) {
      stopwatch.stop();
      debugPrint('SmartVideoInfoException: ${e.message}');
      setState(() {
        _error = e.message;
        _isLoading = false;
      });
    } catch (e, stackTrace) {
      stopwatch.stop();
      debugPrint('Unexpected error: $e');
      debugPrint('Stack trace: $stackTrace');
      setState(() {
        _error = 'Unexpected error: $e';
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Smart Video Info'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Pick Video Button
            FilledButton.icon(
              onPressed: _isLoading ? null : _pickVideo,
              icon: const Icon(Icons.video_library),
              label: const Text('Select Video'),
            ),
            const SizedBox(height: 16),

            // Loading indicator
            if (_isLoading)
              const Center(
                child: Column(
                  children: [
                    CircularProgressIndicator(),
                    SizedBox(height: 8),
                    Text('Extracting metadata...'),
                  ],
                ),
              ),

            // Error message
            if (_error != null)
              Card(
                color: Theme.of(context).colorScheme.errorContainer,
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Row(
                    children: [
                      Icon(
                        Icons.error_outline,
                        color: Theme.of(context).colorScheme.error,
                      ),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          _error!,
                          style: TextStyle(
                            color:
                                Theme.of(context).colorScheme.onErrorContainer,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),

            // Video Info Display
            if (_videoInfo != null) ...[
              // Performance Card
              if (_extractionTimeMs != null)
                Card(
                  color: Theme.of(context).colorScheme.primaryContainer,
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        const Icon(Icons.speed),
                        const SizedBox(width: 8),
                        Text(
                          'Extraction time: ${_extractionTimeMs}ms',
                          style: Theme.of(context).textTheme.titleMedium,
                        ),
                      ],
                    ),
                  ),
                ),
              const SizedBox(height: 8),

              // File path
              if (_selectedPath != null)
                Card(
                  child: Padding(
                    padding: const EdgeInsets.all(12),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'File',
                          style: Theme.of(context).textTheme.labelSmall,
                        ),
                        const SizedBox(height: 4),
                        Text(
                          _selectedPath!.split('/').last,
                          style: Theme.of(context).textTheme.bodyMedium,
                          overflow: TextOverflow.ellipsis,
                        ),
                      ],
                    ),
                  ),
                ),
              const SizedBox(height: 8),

              // Video Properties
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Video',
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      const Divider(),
                      _InfoRow(
                        icon: Icons.aspect_ratio,
                        label: 'Resolution',
                        value: _videoInfo!.resolution,
                      ),
                      _InfoRow(
                        icon: Icons.timer,
                        label: 'Duration',
                        value: _formatDuration(_videoInfo!.duration),
                      ),
                      _InfoRow(
                        icon: Icons.movie,
                        label: 'Codec',
                        value: _videoInfo!.codec,
                      ),
                      _InfoRow(
                        icon: Icons.speed,
                        label: 'FPS',
                        value: _videoInfo!.fps.toStringAsFixed(2),
                      ),
                      _InfoRow(
                        icon: Icons.data_usage,
                        label: 'Bitrate',
                        value: _formatBitrate(_videoInfo!.bitrate),
                      ),
                      _InfoRow(
                        icon: Icons.rotate_right,
                        label: 'Rotation',
                        value: '${_videoInfo!.rotation}°',
                      ),
                      _InfoRow(
                        icon: Icons.folder,
                        label: 'Container',
                        value: _videoInfo!.container,
                      ),
                      _InfoRow(
                        icon: Icons.crop_landscape,
                        label: 'Orientation',
                        value:
                            _videoInfo!.isPortrait ? 'Portrait' : 'Landscape',
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 8),

              // Audio Properties
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Audio',
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      const Divider(),
                      _InfoRow(
                        icon: _videoInfo!.hasAudio
                            ? Icons.volume_up
                            : Icons.volume_off,
                        label: 'Has Audio',
                        value: _videoInfo!.hasAudio ? 'Yes' : 'No',
                      ),
                      if (_videoInfo!.audioCodec != null)
                        _InfoRow(
                          icon: Icons.audiotrack,
                          label: 'Audio Codec',
                          value: _videoInfo!.audioCodec!,
                        ),
                      if (_videoInfo!.sampleRate != null)
                        _InfoRow(
                          icon: Icons.graphic_eq,
                          label: 'Sample Rate',
                          value: '${_videoInfo!.sampleRate} Hz',
                        ),
                      if (_videoInfo!.channels != null)
                        _InfoRow(
                          icon: Icons.surround_sound,
                          label: 'Channels',
                          value: _videoInfo!.channels == 2
                              ? 'Stereo (2)'
                              : '${_videoInfo!.channels}',
                        ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 8),

              // Other Properties
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Other',
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      const Divider(),
                      _InfoRow(
                        icon: Icons.subtitles,
                        label: 'Subtitles',
                        value: _videoInfo!.hasSubtitles ? 'Yes' : 'No',
                      ),
                      _InfoRow(
                        icon: Icons.stream,
                        label: 'Stream Count',
                        value: '${_videoInfo!.streamCount}',
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  String _formatDuration(Duration duration) {
    final hours = duration.inHours;
    final minutes = duration.inMinutes.remainder(60);
    final seconds = duration.inSeconds.remainder(60);

    if (hours > 0) {
      return '$hours:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
    }
    return '$minutes:${seconds.toString().padLeft(2, '0')}';
  }

  String _formatBitrate(int bitrate) {
    if (bitrate >= 1000000) {
      return '${(bitrate / 1000000).toStringAsFixed(2)} Mbps';
    } else if (bitrate >= 1000) {
      return '${(bitrate / 1000).toStringAsFixed(0)} Kbps';
    }
    return '$bitrate bps';
  }
}

class _InfoRow extends StatelessWidget {
  final IconData icon;
  final String label;
  final String value;

  const _InfoRow({
    required this.icon,
    required this.label,
    required this.value,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 6),
      child: Row(
        children: [
          Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
          const SizedBox(width: 12),
          Text(
            label,
            style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                  color: Theme.of(context).colorScheme.onSurfaceVariant,
                ),
          ),
          const Spacer(),
          Text(
            value,
            style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                  fontWeight: FontWeight.w600,
                ),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

Ultra-fast video metadata extraction powered by Smart FFmpeg Engine. No CLI, no process spawning - direct native FFmpeg API.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on smart_video_info

Packages that implement smart_video_info