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

Fast OpenSubtitles hashing (128 KB only). Built-in movie picker for all platforms. Android: Play-safe system Documents default, optional SAF branded folder browser, legacy MediaStore when the host dec [...]

opensubtitles_hasher #

pub.dev License: MIT

Fast OpenSubtitles hashing and movie picking for Flutter - all platforms, one API.

  • Instant hash - reads only 128 KB, even for 50 GB files
  • Built-in picker - works on Android, iOS, macOS, Windows, and Linux
  • Play-safe by default - Android uses the system Documents UI (no READ_MEDIA_VIDEO)
  • Optional branded browser - SAF folder grant (safFolder) for custom video-only UI
  • Zero-copy - Android content:// URI → native Kotlin hash (no full-file copy)

Install #

dependencies:
  opensubtitles_hasher: ^1.2.0
flutter pub get

Quick start #

Pick and hash (any platform) #

import 'package:opensubtitles_hasher/opensubtitles_hasher.dart';

final picked = await OpenSubtitlesHasher.pickAndHash();
if (picked != null) {
  print(picked.hash.hash);       // e.g. 8e245d9679d31e12
  print(picked.hash.fileSize);   // bytes
  print(picked.hash.toApiMap()); // {moviehash, moviebytesize} for the API
}

That's it. No file_picker code, no platform checks - the package handles everything.

Just pick #

final movie = await OpenSubtitlesHasher.pickMovie();
if (movie == null) return; // user cancelled

final result = await OpenSubtitlesHasher.computeHashResult(movie.effectivePath);

Just hash (path you already have) #

final result = await OpenSubtitlesHasher.computeHashResult('/path/to/movie.mkv');
print(result.hash);
print(result.toApiMap()); // ready for the OpenSubtitles API

Platform behaviour #

Platform Picker Hash
Android See Android picker modes below Native Kotlin (zero-copy content://)
iOS / macOS / Windows / Linux System file dialog (video extensions) Pure Dart
Web Not supported Not supported

On other platforms the system file dialog filters by video extension. Size limits are checked immediately after selection. Duration limits cannot be checked because system file dialogs do not provide media duration metadata.


Android picker modes #

Choosing a mode in Dart does not change which permissions land in your AAB. Permissions come only from merged manifests. This plugin never declares READ_MEDIA_VIDEO.

Mode Play Store Custom UI Permission
systemDocuments (default) Yes No (system UI) None
safFolder Yes Yes (branded, video-only) Folder grant via SAF
mediaStore Usually no Yes (whole-device MediaStore) Host app must declare storage / READ_MEDIA_VIDEO

Default — system Documents UI #

await OpenSubtitlesHasher.pickMovie(); // MoviePickerMode.systemDocuments

One system file dialog. Google Play safe.

User grants a Movies / Downloads folder once; your themed folder → video list opens. The grant is remembered for later picks.

await OpenSubtitlesHasher.pickMovie(
  options: MoviePickerOptions.defaults.copyWith(
    mode: MoviePickerMode.safFolder,
    minDuration: Duration(minutes: 30),
    toolbarColorHex: '#1F6FEB',
    toolbarOnColorHex: '#FFFFFF',
    statusBarColorHex: '#174EA6',
    accentColorHex: '#58A6FF',
  ),
);

Legacy — MediaStore (host permission required) #

Use only for sideload / non-Play builds. Add permissions in your app manifest (not this plugin):

<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission
    android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
await OpenSubtitlesHasher.pickMovie(
  options: MoviePickerOptions.defaults.copyWith(
    mode: MoviePickerMode.mediaStore,
  ),
);

When mediaStore is selected, the plugin requests the runtime permission. If the host never declared it, or the user denies, the package falls back to systemDocuments.


Options reference #

await OpenSubtitlesHasher.pickMovie(
  options: MoviePickerOptions.defaults.copyWith(
    mode: MoviePickerMode.safFolder,
    mimeTypes: ['video/mp4', 'video/x-matroska'],
    minSizeBytes: 50 * 1024 * 1024,
    minDuration: Duration(minutes: 30),
    toolbarColorHex: '#1F6FEB',
    toolbarOnColorHex: '#FFFFFF',
    statusBarColorHex: '#174EA6',
    accentColorHex: '#58A6FF',
  ),
);
Option What it does
mode systemDocuments · safFolder · mediaStore
mimeTypes Which video types to show (default: video/*)
minSizeBytes / maxSizeBytes Size limits (null = off)
minDuration / maxDuration Duration limits (null = off)
toolbarColorHex Folder browser toolbar colour (safFolder / mediaStore)
toolbarOnColorHex Toolbar title + icon colour
statusBarColorHex Status bar colour for the picker screen
accentColorHex Folder icon / highlight colour
takePersistablePermission Keep URI readable later (Documents / SAF)

PickedMovie #

final movie = await OpenSubtitlesHasher.pickMovie();
if (movie != null) {
  movie.effectivePath; // pass this to computeHashResult - works on all platforms
  movie.uri;           // Android content:// URI (null on other platforms)
  movie.path;          // Filesystem path (iOS, macOS, Windows, Linux)
  movie.name;          // Display name
  movie.size;          // Bytes (if reported)
  movie.duration;      // Duration (Android folder browsers when available)
}

Always use effectivePath with computeHashResult - it picks uri on Android (zero-copy) or path elsewhere.

picked.searchTitle is a cleaned title from the filename (The.Matrix.1999.1080p.mkvThe Matrix). Use it for an OpenSubtitles text search when the hash has no match. Episode files also expose picked.parsedName.season and episode.


API reference #

Method What
pickMovie({options}) Pick a movie on any platform → PickedMovie?
pickAndHash({options}) Pick + hash in one call → ({PickedMovie, HashResult})?
computeHashResult(pathOrUri) Hash + size + toApiMap()
computeHash(pathOrUri) Hash string only
computeHashSync(path) Synchronous Dart hash (filesystem paths only)
computeFileHash(file) Hash from a File object
isValidHash(hash) True if 16 lowercase hex chars
isContentUri(path) True if content:// URI
parseFileName(name) Title, year, season, episode from a filename

Types: HashResult, PickedMovie, ParsedVideoName, MoviePickerOptions, MoviePickerMode, InvalidFileException, MovieFilterException.


Error handling #

try {
  final picked = await OpenSubtitlesHasher.pickAndHash();
} on MovieFilterException catch (e) {
  // Picked file failed a size/duration filter
  print(e.code);    // TOO_SMALL, TOO_SHORT, etc.
  print(e.message);
} on InvalidFileException {
  // file is smaller than 64 KB - cannot hash
} on PlatformException {
  // native Android error
} on UnsupportedError {
  // web (not supported)
}

How the hash works #

OpenSubtitles identifies files with a tiny checksum:

hash = fileSize
     + sum(first 64 KB as little-endian uint64)
     + sum(last  64 KB as little-endian uint64)
→ 16 lowercase hex digits

Only 128 KB is read regardless of file size. Files under 64 KB raise InvalidFileException.


Try the example #

cd example
flutter run

On Android: pick a mode (systemDocuments / safFolder / mediaStore), step min duration / min size, pick video types, then tap Pick movie + hash.


License #

MIT - see LICENSE.

1
likes
140
points
71
downloads

Documentation

API reference

Publisher

verified publisherdarkmintis.dev

Weekly Downloads

Fast OpenSubtitles hashing (128 KB only). Built-in movie picker for all platforms. Android: Play-safe system Documents default, optional SAF branded folder browser, legacy MediaStore when the host declares storage permissions.

Repository (GitHub)
View/report issues

Topics

#opensubtitles #hash #subtitle #video #media

License

MIT (license)

Dependencies

file_picker, flutter

More

Packages that depend on opensubtitles_hasher

Packages that implement opensubtitles_hasher