| 2091 | |
| 2092 | == Dart == |
| 2093 | Taken from https://github.com/NBTX/opensubtitles_hash/blob/master/opensubtitles_hash.dart |
| 2094 | Check also https://pub.dartlang.org/packages/opensubtitles_hash |
| 2095 | |
| 2096 | /// |
| 2097 | /// Copyright 2019 SamJakob (NBTX, ApolloTV) |
| 2098 | /// |
| 2099 | /// Permission is hereby granted, free of charge, to any person |
| 2100 | /// obtaining a copy of this software and associated documentation |
| 2101 | /// files (the "Software"), to deal in the Software without |
| 2102 | /// restriction, including without limitation the rights to use, |
| 2103 | /// copy, modify, merge, publish, distribute, sublicense, and/or |
| 2104 | /// sell copies of the Software, and to permit persons to whom the |
| 2105 | /// Software is furnished to do so, subject to the following |
| 2106 | /// conditions: |
| 2107 | /// |
| 2108 | /// The above copyright notice and this permission notice shall be |
| 2109 | /// included in all copies or substantial portions of the Software. |
| 2110 | /// |
| 2111 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 2112 | /// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
| 2113 | /// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 2114 | /// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 2115 | /// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 2116 | /// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 2117 | /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| 2118 | /// OTHER DEALINGS IN THE SOFTWARE. |
| 2119 | |
| 2120 | library opensubtitles_hash; |
| 2121 | |
| 2122 | import 'dart:async'; |
| 2123 | import 'dart:io'; |
| 2124 | |
| 2125 | /// |
| 2126 | /// Simple Dart implementation of the OpenSubtitles hash algorithm. |
| 2127 | /// |
| 2128 | /// Hash code is based on Media Player Classic. In natural language |
| 2129 | /// it calculates: size + 64bit checksum of the first and last 64k |
| 2130 | /// |
| 2131 | /// (even if they overlap because the file is smaller than |
| 2132 | /// * 128k). |
| 2133 | /// |
| 2134 | /// Example Usage: |
| 2135 | /// -------------- |
| 2136 | /// String hash = await OpenSubtitlesHasher.computeFileHash( |
| 2137 | /// new File('./breakdance.avi') |
| 2138 | /// ); |
| 2139 | /// |
| 2140 | /// String hash = await OpenSubtitlesHasher.computeURLHash( |
| 2141 | /// "http://www.opensubtitles.org/addons/avi/breakdance.avi" |
| 2142 | /// ) |
| 2143 | /// |
| 2144 | class OpenSubtitlesHasher { |
| 2145 | |
| 2146 | // 64 * 1024 |
| 2147 | static const HASH_CHUNK_SIZE = 65536; |
| 2148 | |
| 2149 | static Future<String> computeURLHash(String url, { |
| 2150 | Map<String, dynamic> headers, |
| 2151 | bool followRedirects = false |
| 2152 | }) async { |
| 2153 | |
| 2154 | HttpClient client = new HttpClient(); |
| 2155 | HttpClientResponse response; |
| 2156 | response = await client.getUrl(Uri.parse(url)) |
| 2157 | .then((HttpClientRequest request){ |
| 2158 | // Add headers if they have been provided... |
| 2159 | if(headers != null){ |
| 2160 | headers.forEach((String name, dynamic value){ |
| 2161 | request.headers.add(name, value.toString()); |
| 2162 | }); |
| 2163 | } |
| 2164 | |
| 2165 | request.followRedirects = followRedirects; |
| 2166 | return request.close(); |
| 2167 | }); |
| 2168 | |
| 2169 | List<List<int>> responseData = await response.toList(); |
| 2170 | return await computeHash( |
| 2171 | responseData.expand((i) => i).toList() |
| 2172 | ); |
| 2173 | |
| 2174 | } |
| 2175 | |
| 2176 | static Future<String> computeFileHash(File file) async { |
| 2177 | return await computeHash(await file.readAsBytes()); |
| 2178 | } |
| 2179 | |
| 2180 | /// |
| 2181 | /// Computes the hash of the byte array parameter. |
| 2182 | /// |
| 2183 | /// This is used by [computeURLHash] and [computeFileHash]. |
| 2184 | /// |
| 2185 | static Future<String> computeHash(List<int> subtitleFile) async { |
| 2186 | List<int> data = new List.filled(8, 0, growable: true); |
| 2187 | |
| 2188 | /// Perform a shift on the first 8 entries |
| 2189 | /// in [data] relative to the size of the file. |
| 2190 | int temp = subtitleFile.length; |
| 2191 | for(var i = 0; i < 8; i++){ |
| 2192 | data[i] = temp & 255; |
| 2193 | temp = temp >> 8; |
| 2194 | } |
| 2195 | |
| 2196 | /// Read bytes from 0 to [HASH_CHUNK_SIZE] |
| 2197 | /// and add the value to the corresponding |
| 2198 | /// position in [data]. |
| 2199 | for(var i = 0; i < HASH_CHUNK_SIZE; i++){ |
| 2200 | data[(i + 8) % 8] += subtitleFile[i]; |
| 2201 | } |
| 2202 | |
| 2203 | /// Read the last [HASH_CHUNK_SIZE] bytes |
| 2204 | /// and add the value to the corresponding |
| 2205 | /// position in [data]. |
| 2206 | int startReference = subtitleFile.length - HASH_CHUNK_SIZE; |
| 2207 | for(var i = startReference; i < subtitleFile.length; i++){ |
| 2208 | int fileOffset = i - startReference; |
| 2209 | data[(fileOffset + 8) % 8] += subtitleFile[i]; |
| 2210 | } |
| 2211 | |
| 2212 | return _binl2hex(data); |
| 2213 | } |
| 2214 | |
| 2215 | static _binl2hex(a) { |
| 2216 | var b = 255, |
| 2217 | c = 7, |
| 2218 | d = '0123456789abcdef', |
| 2219 | e = ''; |
| 2220 | |
| 2221 | a[1] += a[0] >> 8; |
| 2222 | a[0] = a[0] & b; |
| 2223 | a[2] += a[1] >> 8; |
| 2224 | a[1] = a[1] & b; |
| 2225 | a[3] += a[2] >> 8; |
| 2226 | a[2] = a[2] & b; |
| 2227 | a[4] += a[3] >> 8; |
| 2228 | a[3] = a[3] & b; |
| 2229 | a[5] += a[4] >> 8; |
| 2230 | a[4] = a[4] & b; |
| 2231 | a[6] += a[5] >> 8; |
| 2232 | a[5] = a[5] & b; |
| 2233 | a[7] += a[6] >> 8; |
| 2234 | a[6] = a[6] & b; |
| 2235 | a[7] = a[7] & b; |
| 2236 | for (c; c > -1; c--) { |
| 2237 | e += d[(a[c] >> 4 & 15)] + d[(a[c] & 15)]; |
| 2238 | } |
| 2239 | return e; |
| 2240 | } |
| 2241 | |
| 2242 | } |