mirror of
				https://github.com/hydralauncher/hydra.git
				synced 2025-03-09 15:40:26 +00:00 
			
		
		
		
	feat: process profile image
This commit is contained in:
		
							parent
							
								
									a295003ad4
								
							
						
					
					
						commit
						797a09f583
					
				
					 12 changed files with 207 additions and 104 deletions
				
			
		| 
						 | 
					@ -53,7 +53,6 @@
 | 
				
			||||||
    "electron-log": "^5.1.4",
 | 
					    "electron-log": "^5.1.4",
 | 
				
			||||||
    "electron-updater": "^6.1.8",
 | 
					    "electron-updater": "^6.1.8",
 | 
				
			||||||
    "fetch-cookie": "^3.0.1",
 | 
					    "fetch-cookie": "^3.0.1",
 | 
				
			||||||
    "file-type": "^19.0.0",
 | 
					 | 
				
			||||||
    "flexsearch": "^0.7.43",
 | 
					    "flexsearch": "^0.7.43",
 | 
				
			||||||
    "i18next": "^23.11.2",
 | 
					    "i18next": "^23.11.2",
 | 
				
			||||||
    "i18next-browser-languagedetector": "^7.2.1",
 | 
					    "i18next-browser-languagedetector": "^7.2.1",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,3 +4,4 @@ cx_Logging; sys_platform == 'win32'
 | 
				
			||||||
lief; sys_platform == 'win32'
 | 
					lief; sys_platform == 'win32'
 | 
				
			||||||
pywin32; sys_platform == 'win32'
 | 
					pywin32; sys_platform == 'win32'
 | 
				
			||||||
psutil
 | 
					psutil
 | 
				
			||||||
 | 
					Pillow
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,6 +53,7 @@ import "./profile/get-me";
 | 
				
			||||||
import "./profile/undo-friendship";
 | 
					import "./profile/undo-friendship";
 | 
				
			||||||
import "./profile/update-friend-request";
 | 
					import "./profile/update-friend-request";
 | 
				
			||||||
import "./profile/update-profile";
 | 
					import "./profile/update-profile";
 | 
				
			||||||
 | 
					import "./profile/process-profile-image";
 | 
				
			||||||
import "./profile/send-friend-request";
 | 
					import "./profile/send-friend-request";
 | 
				
			||||||
import { isPortableVersion } from "@main/helpers";
 | 
					import { isPortableVersion } from "@main/helpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/main/events/profile/process-profile-image.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/main/events/profile/process-profile-image.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					import { registerEvent } from "../register-event";
 | 
				
			||||||
 | 
					import { PythonInstance } from "@main/services";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const processProfileImage = async (
 | 
				
			||||||
 | 
					  _event: Electron.IpcMainInvokeEvent,
 | 
				
			||||||
 | 
					  path: string
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  return await PythonInstance.processProfileImage(path);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					registerEvent("processProfileImage", processProfileImage);
 | 
				
			||||||
| 
						 | 
					@ -1,46 +1,54 @@
 | 
				
			||||||
import { registerEvent } from "../register-event";
 | 
					import { registerEvent } from "../register-event";
 | 
				
			||||||
import { HydraApi } from "@main/services";
 | 
					import { HydraApi, PythonInstance } from "@main/services";
 | 
				
			||||||
import axios from "axios";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					import fs from "node:fs";
 | 
				
			||||||
import path from "node:path";
 | 
					import path from "node:path";
 | 
				
			||||||
import { fileTypeFromFile } from "file-type";
 | 
					import type { UpdateProfileProps, UserProfile } from "@types";
 | 
				
			||||||
import { UpdateProfileProps, UserProfile } from "@types";
 | 
					import { omit } from "lodash-es";
 | 
				
			||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface PresignedResponse {
 | 
				
			||||||
 | 
					  presignedUrl: string;
 | 
				
			||||||
 | 
					  profileImageUrl: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const patchUserProfile = async (updateProfile: UpdateProfileProps) => {
 | 
					const patchUserProfile = async (updateProfile: UpdateProfileProps) => {
 | 
				
			||||||
  return HydraApi.patch("/profile", updateProfile);
 | 
					  return HydraApi.patch<UserProfile>("/profile", updateProfile);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getNewProfileImageUrl = async (localImageUrl: string) => {
 | 
				
			||||||
 | 
					  const { imagePath, mimeType } =
 | 
				
			||||||
 | 
					    await PythonInstance.processProfileImage(localImageUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const stats = fs.statSync(imagePath);
 | 
				
			||||||
 | 
					  const fileBuffer = fs.readFileSync(imagePath);
 | 
				
			||||||
 | 
					  const fileSizeInBytes = stats.size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { presignedUrl, profileImageUrl } =
 | 
				
			||||||
 | 
					    await HydraApi.post<PresignedResponse>(`/presigned-urls/profile-image`, {
 | 
				
			||||||
 | 
					      imageExt: path.extname(imagePath).slice(1),
 | 
				
			||||||
 | 
					      imageLength: fileSizeInBytes,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await axios.put(presignedUrl, fileBuffer, {
 | 
				
			||||||
 | 
					    headers: {
 | 
				
			||||||
 | 
					      "Content-Type": mimeType,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return profileImageUrl;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateProfile = async (
 | 
					const updateProfile = async (
 | 
				
			||||||
  _event: Electron.IpcMainInvokeEvent,
 | 
					  _event: Electron.IpcMainInvokeEvent,
 | 
				
			||||||
  updateProfile: UpdateProfileProps
 | 
					  updateProfile: UpdateProfileProps
 | 
				
			||||||
): Promise<UserProfile> => {
 | 
					) => {
 | 
				
			||||||
  if (!updateProfile.profileImageUrl) {
 | 
					  if (!updateProfile.profileImageUrl) {
 | 
				
			||||||
    return patchUserProfile(updateProfile);
 | 
					    return patchUserProfile(omit(updateProfile, "profileImageUrl"));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const newProfileImagePath = updateProfile.profileImageUrl;
 | 
					  const profileImageUrl = await getNewProfileImageUrl(
 | 
				
			||||||
 | 
					    updateProfile.profileImageUrl
 | 
				
			||||||
  const stats = fs.statSync(newProfileImagePath);
 | 
					  ).catch(() => undefined);
 | 
				
			||||||
  const fileBuffer = fs.readFileSync(newProfileImagePath);
 | 
					 | 
				
			||||||
  const fileSizeInBytes = stats.size;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const profileImageUrl = await HydraApi.post(`/presigned-urls/profile-image`, {
 | 
					 | 
				
			||||||
    imageExt: path.extname(newProfileImagePath).slice(1),
 | 
					 | 
				
			||||||
    imageLength: fileSizeInBytes,
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
    .then(async (preSignedResponse) => {
 | 
					 | 
				
			||||||
      const { presignedUrl, profileImageUrl } = preSignedResponse;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const mimeType = await fileTypeFromFile(newProfileImagePath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await axios.put(presignedUrl, fileBuffer, {
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          "Content-Type": mimeType?.mime,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      return profileImageUrl as string;
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .catch(() => undefined);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return patchUserProfile({ ...updateProfile, profileImageUrl });
 | 
					  return patchUserProfile({ ...updateProfile, profileImageUrl });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -166,6 +166,14 @@ export class PythonInstance {
 | 
				
			||||||
    this.downloadingGameId = -1;
 | 
					    this.downloadingGameId = -1;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static async processProfileImage(imagePath: string) {
 | 
				
			||||||
 | 
					    return this.rpc
 | 
				
			||||||
 | 
					      .post<{ imagePath: string; mimeType: string }>("/profile-image", {
 | 
				
			||||||
 | 
					        image_path: imagePath,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .then((response) => response.data);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static async handleRpcError(_error: unknown) {
 | 
					  private static async handleRpcError(_error: unknown) {
 | 
				
			||||||
    await this.rpc.get("/healthcheck").catch(() => {
 | 
					    await this.rpc.get("/healthcheck").catch(() => {
 | 
				
			||||||
      logger.error(
 | 
					      logger.error(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,6 +141,8 @@ contextBridge.exposeInMainWorld("electron", {
 | 
				
			||||||
    ipcRenderer.invoke("undoFriendship", userId),
 | 
					    ipcRenderer.invoke("undoFriendship", userId),
 | 
				
			||||||
  updateProfile: (updateProfile: UpdateProfileProps) =>
 | 
					  updateProfile: (updateProfile: UpdateProfileProps) =>
 | 
				
			||||||
    ipcRenderer.invoke("updateProfile", updateProfile),
 | 
					    ipcRenderer.invoke("updateProfile", updateProfile),
 | 
				
			||||||
 | 
					  processProfileImage: (imagePath: string) =>
 | 
				
			||||||
 | 
					    ipcRenderer.invoke("processProfileImage", imagePath),
 | 
				
			||||||
  getFriendRequests: () => ipcRenderer.invoke("getFriendRequests"),
 | 
					  getFriendRequests: () => ipcRenderer.invoke("getFriendRequests"),
 | 
				
			||||||
  updateFriendRequest: (userId: string, action: FriendRequestAction) =>
 | 
					  updateFriendRequest: (userId: string, action: FriendRequestAction) =>
 | 
				
			||||||
    ipcRenderer.invoke("updateFriendRequest", userId, action),
 | 
					    ipcRenderer.invoke("updateFriendRequest", userId, action),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/renderer/src/declaration.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/renderer/src/declaration.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -144,6 +144,9 @@ declare global {
 | 
				
			||||||
    getMe: () => Promise<UserProfile | null>;
 | 
					    getMe: () => Promise<UserProfile | null>;
 | 
				
			||||||
    undoFriendship: (userId: string) => Promise<void>;
 | 
					    undoFriendship: (userId: string) => Promise<void>;
 | 
				
			||||||
    updateProfile: (updateProfile: UpdateProfileProps) => Promise<UserProfile>;
 | 
					    updateProfile: (updateProfile: UpdateProfileProps) => Promise<UserProfile>;
 | 
				
			||||||
 | 
					    processProfileImage: (
 | 
				
			||||||
 | 
					      path: string
 | 
				
			||||||
 | 
					    ) => Promise<{ imagePath: string; mimeType: string }>;
 | 
				
			||||||
    getFriendRequests: () => Promise<FriendRequest[]>;
 | 
					    getFriendRequests: () => Promise<FriendRequest[]>;
 | 
				
			||||||
    updateFriendRequest: (
 | 
					    updateFriendRequest: (
 | 
				
			||||||
      userId: string,
 | 
					      userId: string,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,8 @@ import { UserProfile } from "@types";
 | 
				
			||||||
import { useEffect, useMemo, useState } from "react";
 | 
					import { useEffect, useMemo, useState } from "react";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
import * as styles from "../user.css";
 | 
					import * as styles from "../user.css";
 | 
				
			||||||
import { SPACING_UNIT } from "@renderer/theme.css";
 | 
					import { SPACING_UNIT, vars } from "@renderer/theme.css";
 | 
				
			||||||
 | 
					import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface UserEditProfileProps {
 | 
					export interface UserEditProfileProps {
 | 
				
			||||||
  userProfile: UserProfile;
 | 
					  userProfile: UserProfile;
 | 
				
			||||||
| 
						 | 
					@ -21,9 +22,10 @@ export const UserEditProfile = ({
 | 
				
			||||||
  const [form, setForm] = useState({
 | 
					  const [form, setForm] = useState({
 | 
				
			||||||
    displayName: userProfile.displayName,
 | 
					    displayName: userProfile.displayName,
 | 
				
			||||||
    profileVisibility: userProfile.profileVisibility,
 | 
					    profileVisibility: userProfile.profileVisibility,
 | 
				
			||||||
    imageProfileUrl: null as string | null,
 | 
					    profileImageUrl: null as string | null,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  const [isSaving, setIsSaving] = useState(false);
 | 
					  const [isSaving, setIsSaving] = useState(false);
 | 
				
			||||||
 | 
					  const [isLoadingImage, setIsLoadingImage] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { patchUser } = useUserDetails();
 | 
					  const { patchUser } = useUserDetails();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,9 +55,16 @@ export const UserEditProfile = ({
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (filePaths && filePaths.length > 0) {
 | 
					    if (filePaths && filePaths.length > 0) {
 | 
				
			||||||
      const path = filePaths[0];
 | 
					      setIsLoadingImage(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      setForm({ ...form, imageProfileUrl: path });
 | 
					      const { imagePath } = await window.electron
 | 
				
			||||||
 | 
					        .processProfileImage(filePaths[0])
 | 
				
			||||||
 | 
					        .catch(() => {
 | 
				
			||||||
 | 
					          return { imagePath: null };
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .finally(() => setIsLoadingImage(false));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setForm({ ...form, profileImageUrl: imagePath });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,13 +94,32 @@ export const UserEditProfile = ({
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const avatarUrl = useMemo(() => {
 | 
					  const profileImageUrl = useMemo(() => {
 | 
				
			||||||
    if (form.imageProfileUrl) return `local:${form.imageProfileUrl}`;
 | 
					    if (form.profileImageUrl) return `local:${form.profileImageUrl}`;
 | 
				
			||||||
    if (userProfile.profileImageUrl) return userProfile.profileImageUrl;
 | 
					    if (userProfile.profileImageUrl) return userProfile.profileImageUrl;
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  }, [form, userProfile]);
 | 
					  }, [form, userProfile]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const profileImageContent = () => {
 | 
				
			||||||
 | 
					    if (isLoadingImage) {
 | 
				
			||||||
 | 
					      return <Skeleton className={styles.profileAvatar} />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (profileImageUrl) {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
 | 
					        <img
 | 
				
			||||||
 | 
					          className={styles.profileAvatar}
 | 
				
			||||||
 | 
					          alt={userProfile.displayName}
 | 
				
			||||||
 | 
					          src={profileImageUrl}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <PersonIcon size={96} />;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
 | 
				
			||||||
      <form
 | 
					      <form
 | 
				
			||||||
        onSubmit={handleSaveProfile}
 | 
					        onSubmit={handleSaveProfile}
 | 
				
			||||||
        style={{
 | 
					        style={{
 | 
				
			||||||
| 
						 | 
					@ -107,15 +135,7 @@ export const UserEditProfile = ({
 | 
				
			||||||
          className={styles.profileAvatarEditContainer}
 | 
					          className={styles.profileAvatarEditContainer}
 | 
				
			||||||
          onClick={handleChangeProfileAvatar}
 | 
					          onClick={handleChangeProfileAvatar}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
        {avatarUrl ? (
 | 
					          {profileImageContent()}
 | 
				
			||||||
          <img
 | 
					 | 
				
			||||||
            className={styles.profileAvatar}
 | 
					 | 
				
			||||||
            alt={userProfile.displayName}
 | 
					 | 
				
			||||||
            src={avatarUrl}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        ) : (
 | 
					 | 
				
			||||||
          <PersonIcon size={96} />
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
          <div className={styles.editProfileImageBadge}>
 | 
					          <div className={styles.editProfileImageBadge}>
 | 
				
			||||||
            <DeviceCameraIcon size={16} />
 | 
					            <DeviceCameraIcon size={16} />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
| 
						 | 
					@ -146,5 +166,6 @@ export const UserEditProfile = ({
 | 
				
			||||||
          {isSaving ? t("saving") : t("save")}
 | 
					          {isSaving ? t("saving") : t("save")}
 | 
				
			||||||
        </Button>
 | 
					        </Button>
 | 
				
			||||||
      </form>
 | 
					      </form>
 | 
				
			||||||
 | 
					    </SkeletonTheme>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import json
 | 
				
			||||||
import urllib.parse
 | 
					import urllib.parse
 | 
				
			||||||
import psutil
 | 
					import psutil
 | 
				
			||||||
from torrent_downloader import TorrentDownloader
 | 
					from torrent_downloader import TorrentDownloader
 | 
				
			||||||
 | 
					from profile_image_processor import ProfileImageProcessor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
torrent_port = sys.argv[1]
 | 
					torrent_port = sys.argv[1]
 | 
				
			||||||
http_port = sys.argv[2]
 | 
					http_port = sys.argv[2]
 | 
				
			||||||
| 
						 | 
					@ -73,7 +74,6 @@ class Handler(BaseHTTPRequestHandler):
 | 
				
			||||||
    def do_POST(self):
 | 
					    def do_POST(self):
 | 
				
			||||||
        global torrent_downloader
 | 
					        global torrent_downloader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.path == "/action":
 | 
					 | 
				
			||||||
        if self.headers.get(self.rpc_password_header) != rpc_password:
 | 
					        if self.headers.get(self.rpc_password_header) != rpc_password:
 | 
				
			||||||
            self.send_response(401)
 | 
					            self.send_response(401)
 | 
				
			||||||
            self.end_headers()
 | 
					            self.end_headers()
 | 
				
			||||||
| 
						 | 
					@ -83,6 +83,21 @@ class Handler(BaseHTTPRequestHandler):
 | 
				
			||||||
        post_data = self.rfile.read(content_length)
 | 
					        post_data = self.rfile.read(content_length)
 | 
				
			||||||
        data = json.loads(post_data.decode('utf-8'))
 | 
					        data = json.loads(post_data.decode('utf-8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.path == "/profile-image":
 | 
				
			||||||
 | 
					            parsed_image_path = data['image_path']
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                parsed_image_path, mime_type = ProfileImageProcessor.process_image(parsed_image_path)
 | 
				
			||||||
 | 
					                self.send_response(200)
 | 
				
			||||||
 | 
					                self.send_header("Content-type", "application/json")
 | 
				
			||||||
 | 
					                self.end_headers()
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                self.wfile.write(json.dumps({'imagePath': parsed_image_path, 'mimeType': mime_type}).encode('utf-8'))
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                self.send_response(400)
 | 
				
			||||||
 | 
					                self.end_headers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif self.path == "/action":
 | 
				
			||||||
            if torrent_downloader is None:
 | 
					            if torrent_downloader is None:
 | 
				
			||||||
                torrent_downloader = TorrentDownloader(torrent_port)
 | 
					                torrent_downloader = TorrentDownloader(torrent_port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,6 +114,10 @@ class Handler(BaseHTTPRequestHandler):
 | 
				
			||||||
            self.send_response(200)
 | 
					            self.send_response(200)
 | 
				
			||||||
            self.end_headers()
 | 
					            self.end_headers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.send_response(404)
 | 
				
			||||||
 | 
					            self.end_headers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    httpd = HTTPServer(("", int(http_port)), Handler)
 | 
					    httpd = HTTPServer(("", int(http_port)), Handler)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										39
									
								
								torrent-client/profile_image_processor.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								torrent-client/profile_image_processor.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					from PIL import Image
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					import os, uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProfileImageProcessor:
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def get_parsed_image_data(image_path):
 | 
				
			||||||
 | 
					        Image.MAX_IMAGE_PIXELS = 933120000
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        image = Image.open(image_path)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            image.seek(1)
 | 
				
			||||||
 | 
					        except EOFError:
 | 
				
			||||||
 | 
					            mime_type = image.get_format_mimetype()
 | 
				
			||||||
 | 
					            return image_path, mime_type
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            newUUID = str(uuid.uuid4())
 | 
				
			||||||
 | 
					            new_image_path = os.path.join(tempfile.gettempdir(), newUUID) + ".webp"
 | 
				
			||||||
 | 
					            image.save(new_image_path)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            new_image = Image.open(new_image_path)
 | 
				
			||||||
 | 
					            mime_type = new_image.get_format_mimetype()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return new_image_path, mime_type
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def process_image(image_path):
 | 
				
			||||||
 | 
					        return ProfileImageProcessor.get_parsed_image_data(image_path)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    result = ProfileImageProcessor.get_parsed_image_data("D:\Imagens\807b5c4b02e765bb4930b7c66662ef4b.gif")
 | 
				
			||||||
 | 
					    print(result)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    result = ProfileImageProcessor.get_parsed_image_data("D:/Imagens/20240416_233352~2.png")
 | 
				
			||||||
 | 
					    print(result)
 | 
				
			||||||
| 
						 | 
					@ -4342,15 +4342,6 @@ file-type@^18.7.0:
 | 
				
			||||||
    strtok3 "^7.0.0"
 | 
					    strtok3 "^7.0.0"
 | 
				
			||||||
    token-types "^5.0.1"
 | 
					    token-types "^5.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
file-type@^19.0.0:
 | 
					 | 
				
			||||||
  version "19.0.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.npmjs.org/file-type/-/file-type-19.0.0.tgz"
 | 
					 | 
				
			||||||
  integrity sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    readable-web-to-node-stream "^3.0.2"
 | 
					 | 
				
			||||||
    strtok3 "^7.0.0"
 | 
					 | 
				
			||||||
    token-types "^5.0.1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
file-uri-to-path@1.0.0:
 | 
					file-uri-to-path@1.0.0:
 | 
				
			||||||
  version "1.0.0"
 | 
					  version "1.0.0"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz"
 | 
					  resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue