From ae5f0b2c0f29c4cf9db237132b106ede9bb1fa7e Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Sat, 31 Jul 2021 12:01:29 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=8D=20CV=20match=20scorer=20WIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/client/actions/fileops.actions.tsx | 36 ++++-- src/client/components/ComicDetail.tsx | 7 +- src/client/components/MatchResult.tsx | 3 + src/client/reducers/comicinfo.reducer.js | 2 +- .../shared/utils/filenameparser.utils.ts | 1 - .../shared/utils/searchmatchscorer.utils.ts | 85 +++++++++++--- src/server/index.ts | 5 +- webpack.config.js | 1 + yarn.lock | 105 +----------------- 10 files changed, 114 insertions(+), 133 deletions(-) diff --git a/package.json b/package.json index b26ec52..b03ce2a 100644 --- a/package.json +++ b/package.json @@ -41,13 +41,13 @@ "final-form": "^4.20.2", "fs-extra": "^9.1.0", "http-response-stream": "^1.0.7", - "imghash": "^0.0.8", "jsdoc": "^3.6.7", "opds-extra": "^3.0.9", "pretty-bytes": "^5.6.0", "react": "^17.0.1", "react-collapsible": "^2.8.3", "react-dom": "^17.0.1", + "react-fast-compare": "^3.2.0", "react-final-form": "^6.5.3", "react-spinners": "^0.11.0", "react-window-dynamic-list": "^2.3.5", diff --git a/src/client/actions/fileops.actions.tsx b/src/client/actions/fileops.actions.tsx index 4d359be..88a2ef0 100644 --- a/src/client/actions/fileops.actions.tsx +++ b/src/client/actions/fileops.actions.tsx @@ -15,7 +15,11 @@ import { CV_CLEANUP, } from "../constants/action-types"; import { refineQuery } from "../shared/utils/filenameparser.utils"; -import { matchScorer } from "../shared/utils/searchmatchscorer.utils"; +import { + matchScorer, + compareCoverImageHashes, +} from "../shared/utils/searchmatchscorer.utils"; +import { assign, each } from "lodash"; export async function walkFolder(path: string): Promise> { return axios @@ -108,7 +112,7 @@ export const getRecentlyImportedComicBooks = (options) => async (dispatch) => { export const fetchComicVineMatches = (searchPayload) => (dispatch) => { try { - const issueString = searchPayload.rawFileDetails.path.split("/").pop(); + const issueString = searchPayload.rawFileDetails.name; const issueSearchQuery: IComicVineSearchQuery = refineQuery(issueString); let seriesSearchQuery: IComicVineSearchQuery = {} as IComicVineSearchQuery; if (searchPayload.rawFileDetails.containedIn !== "comics") { @@ -134,20 +138,27 @@ export const fetchComicVineMatches = (searchPayload) => (dispatch) => { offset: "0", resources: "issue", }, - transformResponse: [ - (r) => { - const searchMatches = JSON.parse(r); - return matchScorer(searchMatches.results, { - issue: issueSearchQuery, - series: seriesSearchQuery, - }); - }, - ], + transformResponse: (r) => JSON.parse(r), }) .then((response) => { + const searchMatches = response.data.results; + each(searchMatches, (match) => assign(match, { score: 0 })); + const results = matchScorer( + searchMatches, + { + issue: issueSearchQuery, + series: seriesSearchQuery, + }, + searchPayload.rawFileDetails, + ); + const scoredResults = compareCoverImageHashes( + searchPayload.rawFileDetails, + results, + ); + dispatch({ type: CV_SEARCH_SUCCESS, - searchResults: response.data, + searchResults: scoredResults, searchQueryObject: { issue: issueSearchQuery, series: seriesSearchQuery, @@ -159,6 +170,7 @@ export const fetchComicVineMatches = (searchPayload) => (dispatch) => { } catch (error) { console.log(error); } + dispatch({ type: CV_CLEANUP, }); diff --git a/src/client/components/ComicDetail.tsx b/src/client/components/ComicDetail.tsx index 2b50fe5..357bf54 100644 --- a/src/client/components/ComicDetail.tsx +++ b/src/client/components/ComicDetail.tsx @@ -7,7 +7,7 @@ import ComicVineSearchForm from "./ComicVineSearchForm"; import { css } from "@emotion/react"; import PuffLoader from "react-spinners/PuffLoader"; -import { isEmpty, isUndefined } from "lodash"; +import { isEmpty, isUndefined, isEqual } from "lodash"; import { IExtractedComicBookCoverFile, RootState } from "threetwo-ui-typings"; import { fetchComicVineMatches } from "../actions/fileops.actions"; import { Drawer, Divider } from "antd"; @@ -27,6 +27,7 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => { const comicVineSearchResults = useSelector( (state: RootState) => state.comicInfo.searchResults, + ); const comicVineSearchQueryObject = useSelector( (state: RootState) => state.comicInfo.searchQuery, @@ -135,8 +136,8 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
{!isEmpty(comicVineSearchResults) && ( - - )} + + )}
diff --git a/src/client/components/MatchResult.tsx b/src/client/components/MatchResult.tsx index 4ca2871..8efc9ca 100644 --- a/src/client/components/MatchResult.tsx +++ b/src/client/components/MatchResult.tsx @@ -16,9 +16,12 @@ export const MatchResult = (props: MatchResultProps) => { {map(props.matchData, (match, idx) => { + return ( + + {match.score} diff --git a/src/client/reducers/comicinfo.reducer.js b/src/client/reducers/comicinfo.reducer.js index 89d65d5..588f6f6 100644 --- a/src/client/reducers/comicinfo.reducer.js +++ b/src/client/reducers/comicinfo.reducer.js @@ -17,7 +17,7 @@ function comicinfoReducer(state = initialState, action) { inProgress: true, }; case CV_SEARCH_SUCCESS: - console.log("ACTION", action); + console.log("ASDASD", action) return { ...state, searchResults: action.searchResults, diff --git a/src/client/shared/utils/filenameparser.utils.ts b/src/client/shared/utils/filenameparser.utils.ts index 05fd213..81405ba 100644 --- a/src/client/shared/utils/filenameparser.utils.ts +++ b/src/client/shared/utils/filenameparser.utils.ts @@ -158,7 +158,6 @@ export const extractNumerals = (inputString: string): MatchArray[string] => { export const refineQuery = (inputString) => { const queryObj = tokenize(inputString); - console.log("QWEQWEQWE", queryObj); const removedYears = xor( queryObj.sentence_tokens.normalized, queryObj.years.yearMatches, diff --git a/src/client/shared/utils/searchmatchscorer.utils.ts b/src/client/shared/utils/searchmatchscorer.utils.ts index eac8afe..2c8f9b5 100644 --- a/src/client/shared/utils/searchmatchscorer.utils.ts +++ b/src/client/shared/utils/searchmatchscorer.utils.ts @@ -1,17 +1,50 @@ -import { each, isUndefined, isNull } from "lodash"; -const stringSimilarity = require("string-similarity"); +/* + * MIT License + * + * Copyright (c) 2015 Rishi Ghan + * + The MIT License (MIT) -export const matchScorer = (searchMatches, searchQuery) => { +Copyright (c) 2015 Rishi Ghan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +/* + * Revision History: + * Initial: 2021/07/29 Rishi Ghan + */ + +import { each, map, isUndefined, isNull, assign } from "lodash"; +const stringSimilarity = require("string-similarity"); +import axios from "axios"; + +export const matchScorer = (searchMatches, searchQuery, rawFileDetails) => { // 1. Check if it exists in the db (score: 0) // 2. Check if issue name matches strongly (score: ++) // 3. Check if issue number matches strongly (score: ++) // 4. Check if issue covers hash match strongly (score: +++) // 5. Check if issue year matches strongly (score: +) - const score = 0; - each(searchMatches, (match, idx) => { - console.log("SEARCH QUERY IN SMS:", searchQuery); - console.log("MATCH NAME:", match); - match.score = 0; + + each(searchMatches, (match, idx) => { + // check for the issue name match + if ( !isNull(searchQuery.issue.searchParams.searchTerms.name) && !isNull(match.name) @@ -21,7 +54,6 @@ export const matchScorer = (searchMatches, searchQuery) => { match.name, ); match.score = issueNameScore; - console.log("name score" + idx + ":", issueNameScore); } // issue number matches @@ -33,12 +65,37 @@ export const matchScorer = (searchMatches, searchQuery) => { parseInt(searchQuery.issue.searchParams.searchTerms.number, 10) === parseInt(match.issue_number, 10) ) { - match.score += 2; - console.log(match.score); + match.score += 1; } } - }); - return searchMatches; - // check for the issue name match + return match; + }); + + return searchMatches; +}; + +export const compareCoverImageHashes = (original, matches) => { + // cover matches + // calculate the image hashes of the covers and compare the Levenshtein Distance + each(matches, async (match) => { + const result = await axios.request({ + url: "http://localhost:3000/api/imagetransformation/calculateLevenshteinDistance", + method: "POST", + data: { + imagePath: original.path, + imagePath2: match.image.small_url, + options: { + match_id: match.id, + }, + }, + }); + + if (result.data.levenshteinDistance === 0) { + match.score += 4; + } else { + match.score -= 4; + } + }); + return matches; }; diff --git a/src/server/index.ts b/src/server/index.ts index 8f762ef..7f698cf 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -26,7 +26,7 @@ const port: number = Number(process.env.PORT) || 8050; // set our port // Send index.html on root request app.use(express.static("dist")); -export function opdsRouter() { +export const opdsRouter = () => { const path_of_books = "/Users/rishi/work/threetwo/src/server/comics"; router.use("/opds", async (req, res, next) => { return buildAsync( @@ -87,7 +87,8 @@ export function opdsRouter() { }); return router; -} +}; + app.get("/", (req: Request, res: Response) => { console.log("sending index.html"); res.sendFile("/dist/index.html"); diff --git a/webpack.config.js b/webpack.config.js index 515e3f1..56644d5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -55,6 +55,7 @@ module.exports = { }, resolve: { extensions: ["*", ".ts", ".tsx", ".js", ".jsx", ".json"], + aliasFields: ["browser", "browser.esm"], }, devServer: { port: 3050, diff --git a/yarn.lock b/yarn.lock index d949594..e94b5d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1057,27 +1057,6 @@ bluebird "^3" fast-glob "^3" -"@canvas/image-data@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@canvas/image-data/-/image-data-1.0.0.tgz#3bd2cd856e13fc9e2c25feff360a4056857b0367" - integrity sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw== - -"@canvas/image@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@canvas/image/-/image-1.0.1.tgz#02bdd4bdf95352b1385e1bd3412fccb31f9269fc" - integrity sha512-nQ6Qt/marzeQkFikxw32qMep5Rs1U/7DdjqHlCn8ck78IX56CFnXjdfRCXOiM4kx8yvS4SmcPXuJjfpczRjFqA== - dependencies: - "@canvas/image-data" "^1.0.0" - "@cwasm/jpeg-turbo" "^0.1.1" - "@cwasm/lodepng" "^0.1.2" - "@cwasm/nsbmp" "^0.1.0" - "@cwasm/nsgif" "^0.1.0" - "@cwasm/webp" "^0.1.3" - fast-base64-decode "^1.0.0" - fast-base64-encode "^1.0.0" - fast-base64-length "^1.0.0" - simple-get "^3.1.0" - "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1091,41 +1070,6 @@ resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f" integrity sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ== -"@cwasm/jpeg-turbo@^0.1.1": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@cwasm/jpeg-turbo/-/jpeg-turbo-0.1.3.tgz#4f5e840d2fc58de9e876738456cd1bcb9c3424a3" - integrity sha512-FkZxwwC6r4zhzlqM0nYGaMj/MDSrZPxLOdPdM6ySlgsMfOpNAZcLQkpNF4jP+DmsuUvRoeUD0YSMBvg3jYfK6w== - dependencies: - "@canvas/image-data" "^1.0.0" - -"@cwasm/lodepng@^0.1.2": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@cwasm/lodepng/-/lodepng-0.1.4.tgz#6ffeb8e9c5922680f89d6f432a9793fd498cb85c" - integrity sha512-UY5iB3ywNTgonNF4RmcxEc/eeNcAZBnX9MlSeNUogyruG7ZNPgpKotwPyiLWbqOjlXULz4wdArPEko0Zm3+cGQ== - dependencies: - "@canvas/image-data" "^1.0.0" - -"@cwasm/nsbmp@^0.1.0": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@cwasm/nsbmp/-/nsbmp-0.1.2.tgz#521479dbcbf2bf2f742f0dd540a8aa9a930b3f9c" - integrity sha512-ZQGNDOI9ZxokqbNPmvcLKR8gBhfHjeFVuqE2eYysrbGiraQ93KeeIVrtiGKOMLW2JJOq9PbGBU0NX47qkpKWWA== - dependencies: - "@canvas/image-data" "^1.0.0" - -"@cwasm/nsgif@^0.1.0": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@cwasm/nsgif/-/nsgif-0.1.2.tgz#0f9687be119f982db1c64340dd2db017bf00cefe" - integrity sha512-LOD5HlL0O5jpnIAl+dLSZcB3v0RBNBjtoaymdCEPe2kyKzaP20BF+jy/QUyOZogQsgMVjusZES3tgwwoiiJ2rA== - dependencies: - "@canvas/image-data" "^1.0.0" - -"@cwasm/webp@^0.1.3": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@cwasm/webp/-/webp-0.1.5.tgz#b61014319aa9bb70b9f6d8a30f2838fed21779d1" - integrity sha512-ceIZQkyxK+s7mmItNcWqqHdOBiJAxYxTnrnPNgUNjldB1M9j+Bp/3eVIVwC8rUFyN/zoFwuT0331pyY3ackaNA== - dependencies: - "@canvas/image-data" "^1.0.0" - "@discoveryjs/json-ext@^0.5.0": version "0.5.3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" @@ -3256,11 +3200,6 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -blockhash-core@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/blockhash-core/-/blockhash-core-0.1.0.tgz#dc25bd864b5af05c33e4266fb4d8f4e619462d04" - integrity sha512-Cv7BgBo0jjVPaeuel4cvxf9LqIGsYNIPz9DAGvvrF9LRlEq9Q3HXu+S8bklPCae0sCxAXic4HGMoImf3FeO3Nw== - bluebird@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -5730,21 +5669,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-base64-decode@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" - integrity sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q== - -fast-base64-encode@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-base64-encode/-/fast-base64-encode-1.0.0.tgz#883945eb67e139dbf5a877bcca57a89e6824c7d4" - integrity sha512-z2XCzVK4fde2cuTEHu2QGkLD6BPtJNKJPn0Z7oINvmhq/quUuIIVPYKUdN0gYeZqOyurjJjBH/bUzK5gafyHvw== - -fast-base64-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-base64-length/-/fast-base64-length-1.0.0.tgz#080ec166a8ef6d77f8f612d9ce629dcaceb13a4a" - integrity sha512-MV+/ioblHx6SMjc/1l4EAnRJyAku6+6DxZ6RW0FoFCF1Aol/Ldb6FqwE3Kn3Ju1aam2m1KCIVoCljhgcG+Umzg== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -5866,7 +5790,7 @@ file-type@5.2.0, file-type@^5.2.0: resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" integrity sha1-LdvqfHP/42No365J3DOMBYwritY= -file-type@^10.10.0, file-type@^10.4.0, file-type@^10.5.0: +file-type@^10.4.0, file-type@^10.5.0: version "10.11.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-10.11.0.tgz#2961d09e4675b9fb9a3ee6b69e9cd23f43fd1890" integrity sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw== @@ -6871,13 +6795,6 @@ image-size@^1.0.0: dependencies: queue "6.0.2" -image-type@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/image-type/-/image-type-4.1.0.tgz#72a88d64ff5021371ed67b9a466442100be57cd1" - integrity sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg== - dependencies: - file-type "^10.10.0" - image-webpack-loader@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/image-webpack-loader/-/image-webpack-loader-7.0.1.tgz#5e5118e36a6ce17d40a0b835fe2bb39fc80eb9c7" @@ -6963,16 +6880,6 @@ imagemin@^7.0.1: p-pipe "^3.0.0" replace-ext "^1.0.0" -imghash@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/imghash/-/imghash-0.0.8.tgz#94455274bb90b4fd082d03db252478b3aed78a53" - integrity sha512-XNDlRVyuQc4HUCKqjV/lNRZST9sJS/EX3nt7YW3RCfr+Dmog5lUm4PNTUdmka0ZKsAWboPw+0Xr/l/jWzFiRPw== - dependencies: - "@canvas/image" "^1.0.0" - blockhash-core "^0.1.0" - image-type "^4.1.0" - jpeg-js "^0.4.1" - "immutable@^3.8.1 || ^4.0.0-rc.1": version "4.0.0-rc.14" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0-rc.14.tgz#29ba96631ec10867d1348515ac4e6bdba462f071" @@ -8035,11 +7942,6 @@ joycon@^2.2.5: resolved "https://registry.yarnpkg.com/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615" integrity sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ== -jpeg-js@^0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" - integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== - js-base64@^2.1.8: version "2.6.4" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" @@ -11335,6 +11237,11 @@ react-dom@^17.0.1: object-assign "^4.1.1" scheduler "^0.20.2" +react-fast-compare@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + react-final-form@^6.5.3: version "6.5.3" resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-6.5.3.tgz#b60955837fe9d777456ae9d9c48e3e2f21547d29"