{"id":23401022,"url":"https://github.com/mrshoenel/ff-fingerprinter","last_synced_at":"2025-04-11T19:12:15.300Z","repository":{"id":52091549,"uuid":"145478916","full_name":"MrShoenel/ff-fingerprinter","owner":"MrShoenel","description":"FF-FingerPrinter is a tool to create fingerprints of media files using their streams and properties thereof.","archived":false,"fork":false,"pushed_at":"2022-12-07T23:27:06.000Z","size":1058,"stargazers_count":3,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-25T15:07:06.454Z","etag":null,"topics":["ffmpeg","ffprobe","fingerprint","fingerprinting","hash","hashing"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MrShoenel.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-20T23:01:09.000Z","updated_at":"2022-11-22T18:09:01.000Z","dependencies_parsed_at":"2022-09-26T16:33:20.276Z","dependency_job_id":null,"html_url":"https://github.com/MrShoenel/ff-fingerprinter","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MrShoenel%2Fff-fingerprinter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MrShoenel%2Fff-fingerprinter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MrShoenel%2Fff-fingerprinter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MrShoenel%2Fff-fingerprinter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MrShoenel","download_url":"https://codeload.github.com/MrShoenel/ff-fingerprinter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248465346,"owners_count":21108244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ffmpeg","ffprobe","fingerprint","fingerprinting","hash","hashing"],"created_at":"2024-12-22T11:13:59.823Z","updated_at":"2025-04-11T19:12:15.276Z","avatar_url":"https://github.com/MrShoenel.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FF-FingerPrinter\nFF-FingerPrinter is a Node.js-based tool that uses `ffmpeg` and `ffprobe` to probe and fingerprint media files (anything that your ffmpeg can read). It supports hashing a media file's chapters and streams (currently video-, audio- and subtitle-streams are supported).\n\n## Install from npm [![Current Version](https://img.shields.io/npm/v/sh.ff-fingerprinter.svg)](https://www.npmjs.com/package/sh.ff-fingerprinter)\nThis package can be installed using the following command: `npm install sh.ff-fingerprinter`.\n\n___\n\nThis tool can be used as a library in your own project or as a standalone `CLI`-application. `JSDoc`-typedefs have been created to model the full output. The library exports the single class `FFFingerPrinter` and some default configurations. The class makes extensive use of `async` features.\n\nThe purpose is to uniquely identify media files and the streams they contain. This helps to identify duplicates and to avoid ID'ing files based on their names/paths or attributes or timestamps (which can easily be changed). Hashing of streams of a file can be done in parallel.\n\nWhen hashing, the raw data of each stream is taken and put through a cryptographic hasher, such as `sha256` (default). Also, properties of streams are hashed _deterministically_ (sorted). Then, a hash for the whole file is computed and the file itself is analyzed using `stat`. A _remux_ of a file (containing the same streams as the original) will be ID'ed differently, but the streams' hashes will be identical (look below for an example output).\n\n# Command Line Interface (CLI)\nHere is how to run FF-FingerPrinter from CLI:\n\n\u003cpre\u003enode ./cli/cli.js -h\n  Usage: cli [options]\n\n  FF-Fingerprinter uses FFmpeg to analyze and fingerprint media files. It provides extensive\n  information as obtained from FFprobe and adds hashes to each stream and the file as a whole.\n  The path to the file must be the last argument.\n\n  Options:\n\n    -v, --version            output the version number\n    -c, --config [config]    Optional. The path to a config-file that exports an instance of\n                             FingerPrintOptions. If not specified, will create a config from\n                             the file config.default.json.\n    -f, --format [format]    Optional. Formats (indents) the output. Has no effect if the output\n                             is only the hash. (default: true)\n    -m, --ffmpeg [ffmpeg]    Optional. The path to FFmpeg; overrides the path defined in the\n                             config.\n    -p, --ffprobe [ffprobe]  Optional. The path to FFprobe; overrides the path defined in the\n                             config.\n    --quiet                  Optional. If provided, will suppress any output to stderr.\n    --only-hash              Optional. If provided, only the value of \"hashAll\" will be printed.\n    --skip-probe             Optional. If provided, will not probe the file for its contents.\n    --skip-hash              Optional. If provided, will only probe the file and skip hashing\n                             entirely.\n    --skip-chapters          Optional. If provided, will not include the chapters in the probing\n                             or hashing.\n    --skip-ffversions        Optional. If provided, will not include the ffmpeg and ffprobe\n                             versions in the output.\n    -h, --help               output usage information\n\u003c/pre\u003e\n\nIn CLI-mode, a configuration file is used. An example can be found in `cli/config.default.js`.\n\n# Example output\nIn CLI-mode, FF-Fingerprinter writes its result to `stdout` while _logging_ what it's doing to `stderr`, so that you can __pipe__ its `JSON`-based output to a file.\n\n\u003cpre\u003e\n\u003cspan style=\"color:blue\"\u003enode ./cli/cli.js 'D:\\media\\MOV_0608.mp4'\u003c/span\u003e\n\u003cspan style=\"color:red\"\u003e2018-8-5 15:22:02 [FFFingerPrinter]: Fingerprinting file: D:\\media\\MOV_0608.mp4\n2018-8-5 15:22:02 [FFFingerPrinter]: Probing..\n2018-8-5 15:22:03 [FFFingerPrinter]: Found 2 streams and 0 chapters.\n2018-8-5 15:22:03 [FFFingerPrinter]: Hashing up to 80000000 bytes of stream #0 (video)..\n2018-8-5 15:22:03 [FFFingerPrinter]: Hashing up to 10000000 bytes of stream #1 (audio)..\n2018-8-5 15:22:03 [FFFingerPrinter]: Hashing for stream #1 (audio) was successful!\n2018-8-5 15:22:03 [FFFingerPrinter]: Hashing for stream #0 (video) was successful!\u003c/span\u003e\n{\n  \"streams\": [\n    {\n      \"index\": 0,\n      \"codec_name\": \"h264\",\n      \"codec_long_name\": \"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10\",\n      \"profile\": \"High\",\n      \"codec_type\": \"video\",\n      \"codec_time_base\": \"5326247/638640000\",\n      \"codec_tag_string\": \"avc1\",\n      \"codec_tag\": \"0x31637661\",\n      \"width\": 1920,\n      \"height\": 1080,\n      \"coded_width\": 1920,\n      \"coded_height\": 1088,\n      \"has_b_frames\": 0,\n      \"sample_aspect_ratio\": \"1:1\",\n      \"display_aspect_ratio\": \"16:9\",\n      \"pix_fmt\": \"yuv420p\",\n      \"level\": 42,\n      \"color_range\": \"tv\",\n      \"color_space\": \"bt709\",\n      \"color_transfer\": \"bt709\",\n      \"color_primaries\": \"bt709\",\n      \"chroma_location\": \"left\",\n      \"refs\": 1,\n      \"is_avc\": \"true\",\n      \"nal_length_size\": \"4\",\n      \"r_frame_rate\": \"60000/1001\",\n      \"avg_frame_rate\": \"319320000/5326247\",\n      \"time_base\": \"1/90000\",\n      \"start_pts\": 0,\n      \"start_time\": \"0.000000\",\n      \"duration_ts\": 5326247,\n      \"duration\": \"59.180522\",\n      \"bit_rate\": \"29976310\",\n      \"bits_per_raw_sample\": \"8\",\n      \"nb_frames\": \"3548\",\n      \"disposition\": {\n        \"default\": 1,\n        \"dub\": 0,\n        \"original\": 0,\n        \"comment\": 0,\n        \"lyrics\": 0,\n        \"karaoke\": 0,\n        \"forced\": 0,\n        \"hearing_impaired\": 0,\n        \"visual_impaired\": 0,\n        \"clean_effects\": 0,\n        \"attached_pic\": 0,\n        \"timed_thumbnails\": 0\n      },\n      \"tags\": {\n        \"creation_time\": \"2018-07-27T10:33:22.000000Z\",\n        \"language\": \"eng\",\n        \"handler_name\": \"VideoHandle\"\n      }\n    },\n    {\n      \"index\": 1,\n      \"codec_name\": \"aac\",\n      \"codec_long_name\": \"AAC (Advanced Audio Coding)\",\n      \"profile\": \"LC\",\n      \"codec_type\": \"audio\",\n      \"codec_time_base\": \"1/48000\",\n      \"codec_tag_string\": \"mp4a\",\n      \"codec_tag\": \"0x6134706d\",\n      \"sample_fmt\": \"fltp\",\n      \"sample_rate\": \"48000\",\n      \"channels\": 2,\n      \"channel_layout\": \"stereo\",\n      \"bits_per_sample\": 0,\n      \"r_frame_rate\": \"0/0\",\n      \"avg_frame_rate\": \"0/0\",\n      \"time_base\": \"1/48000\",\n      \"start_pts\": 0,\n      \"start_time\": \"0.000000\",\n      \"duration_ts\": 2840532,\n      \"duration\": \"59.177750\",\n      \"bit_rate\": \"156002\",\n      \"max_bit_rate\": \"156000\",\n      \"nb_frames\": \"2774\",\n      \"disposition\": {\n        \"default\": 1,\n        \"dub\": 0,\n        \"original\": 0,\n        \"comment\": 0,\n        \"lyrics\": 0,\n        \"karaoke\": 0,\n        \"forced\": 0,\n        \"hearing_impaired\": 0,\n        \"visual_impaired\": 0,\n        \"clean_effects\": 0,\n        \"attached_pic\": 0,\n        \"timed_thumbnails\": 0\n      },\n      \"tags\": {\n        \"creation_time\": \"2018-07-27T10:33:22.000000Z\",\n        \"language\": \"eng\",\n        \"handler_name\": \"SoundHandle\"\n      }\n    }\n  ],\n  \"chapters\": [],\n  \"format\": {\n    \"filename\": \"D:\\\\media\\\\MOV_0608.mp4\",\n    \"nb_streams\": 2,\n    \"nb_programs\": 0,\n    \"format_name\": \"mov,mp4,m4a,3gp,3g2,mj2\",\n    \"format_long_name\": \"QuickTime / MOV\",\n    \"start_time\": \"0.000000\",\n    \"duration\": \"59.183000\",\n    \"size\": \"223716134\",\n    \"bit_rate\": \"30240593\",\n    \"probe_score\": 100,\n    \"tags\": {\n      \"major_brand\": \"mp42\",\n      \"minor_version\": \"0\",\n      \"compatible_brands\": \"isommp42\",\n      \"creation_time\": \"2018-07-27T10:33:22.000000Z\",\n      \"com.android.version\": \"8.0.0\",\n      \"com.android.video.temporal_layers_count\": \"\"\n    }\n  },\n  \"hashInfo\": {\n    \"hashesChapters\": [],\n    \"hashFormat\": \"8592c28336cd8476f7d3b113401df659483648c940f358bd648012ffa4bcd5a3\",\n    \"hashesStreams\": [\n      {\n        \"index\": 1,\n        \"hash\": \"4f3d806978a5613c8a41b9524c034d79e041254f95ee52b01ed021a5a79366f5\",\n        \"numBytes\": 1153984,\n        \"hashWithProps\": \"63765cff913cf485390487068e837b8dd0a9788938a6c8539f38045dc83dbb72\"\n      },\n      {\n        \"index\": 0,\n        \"hash\": \"79e2f1f9f23dc1e8a5f5f37acc9a19c04bfcd6b50d65acfb58525138af7d90d7\",\n        \"numBytes\": 80000000,\n        \"hashWithProps\": \"e03ed2cab4043b6c98d2a6cd26ddfd73c68b1594c31b6cc2c1bc40c5b68ea91c\"\n      }\n    ],\n    \"hashAll\": \"a6a887499e4b6c6ce312eae40f82735650ba1edc263bd7804fabb3296ce05fe6\",\n    \"hashBasedOn\": [\n      \"s:1\",\n      \"s:0\",\n      \"format\"\n    ],\n    \"fileName\": \"MOV_0608.mp4\",\n    \"filePath\": \"D:\\\\media\",\n    \"hashTime\": \"Mon, 20 Aug 2018 22:53:48 GMT\",\n    \"fileTimes\": {\n      \"accessTime\": \"Thu, 02 Aug 2018 18:53:39 GMT\",\n      \"modTime\": \"Fri, 27 Jul 2018 10:33:21 GMT\",\n      \"changeTime\": \"Thu, 02 Aug 2018 18:53:46 GMT\",\n      \"createTime\": \"Thu, 02 Aug 2018 18:53:39 GMT\"\n    },\n    \"fffVersions\": {\n      \"ffFingerprintVersion\": \"1.1.0\",\n      \"ffmpegVersion\": \"ffmpeg version N-91303-g8331e59133 Copyright (c) 2000-2018 the FFmpeg developers; built with gcc 7.3.0 (Rev2, Built by MSYS2 project); libavutil      56. 18.102 / 56. 18.102; libavcodec     58. 20.102 / 58. 20.102; libavformat    58. 17.100 / 58. 17.100; libavdevice    58.  4.101 / 58.  4.101; libavfilter     7. 25.100 /  7. 25.100; libswscale      5.  2.100 /  5.  2.100; libswresample   3.  2.100 /  3.  2.100; libpostproc    55.  2.100 / 55.  2.100; \",\n      \"ffprobeVersion\": \"ffprobe version N-91303-g8331e59133 Copyright (c) 2007-2018 the FFmpeg developers; built with gcc 7.3.0 (Rev2, Built by MSYS2 project); libavutil      56. 18.102 / 56. 18.102; libavcodec     58. 20.102 / 58. 20.102; libavformat    58. 17.100 / 58. 17.100; libavdevice    58.  4.101 / 58.  4.101; libavfilter     7. 25.100 /  7. 25.100; libswscale      5.  2.100 /  5.  2.100; libswresample   3.  2.100 /  3.  2.100; libpostproc    55.  2.100 / 55.  2.100; \"\n    }\n  },\n  \"hashConf\": {\n    \"hashAlgo\": \"sha256\",\n    \"includeChapters\": true,\n    \"mode\": \"fast\",\n    \"modeBytes\": [\n      {\n        \"streamType\": \"audio\",\n        \"bytes\": 10000000\n      },\n      {\n        \"streamType\": \"video\",\n        \"bytes\": 80000000\n      },\n      {\n        \"streamType\": \"subtitle\",\n        \"bytes\": 5000\n      }\n    ],\n    \"numStreamsParallel\": 4,\n    \"streamConf\": {\n      \"types\": [\n        \"audio\",\n        \"video\",\n        \"subtitle\"\n      ],\n      \"ids\": []\n    }\n  },\n  \"fingerprint\": \"a6a887499e4b6c6ce312eae40f82735650ba1edc263bd7804fabb3296ce05fe6\"\n}\n\u003c/pre\u003e\n\nSome noteworthy details:\n* __fingerprint__: The hash of the entire file, taking into account all streams' and chapters' hashes. Replicates the value from `hashInfo.hashAll`.\n* __hashWithProps__: Is a hash over the streams' hash and its (deterministically) stringified properties. `numBytes` indicates how many bytes were read to create the hash.\n* The output of `ffprobe` is fully preserved and additional properties, such as `hashInfo` and `fffVersions` are added. Also, the settings (`hashConf`) used for hashing are preserved, so that the results are __repeatable__.\n\n# Testing\nNote that for testing, you need to have `ffmpeg` and `ffprobe` installed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrshoenel%2Fff-fingerprinter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrshoenel%2Fff-fingerprinter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrshoenel%2Fff-fingerprinter/lists"}