Skip to content

Commit

Permalink
Merge pull request #35 from imxade/version_2
Browse files Browse the repository at this point in the history
  • Loading branch information
Pranav0-0Aggarwal authored Aug 4, 2024
2 parents 8b30fc0 + 2f88f55 commit 39a4efe
Show file tree
Hide file tree
Showing 33 changed files with 2,168 additions and 346 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/artifact.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Build and Upload Artifacts

on:
push:
branches:
- artifact
- version_2
pull_request:
branches:
- main
- master

jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Compose
run: |
sudo apt-get update
sudo apt-get install -y docker-compose
- name: Run Docker Compose
run: |
docker compose -f build_compose.yaml up
- name: Check for available files
run: |
tree .
- name: UploadArtifacts
uses: actions/upload-artifact@v4
with:
name: Artifacts
path: dist/
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

31 changes: 0 additions & 31 deletions README.md

This file was deleted.

3 changes: 1 addition & 2 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ def run():
# height=710,
# width=225,
# frameless=True,
easy_drag=True,
on_top=True
easy_drag=True
)

webview.start()
Expand Down
4 changes: 1 addition & 3 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
web:
develop:
Expand All @@ -16,7 +14,7 @@ services:
source: ./
target: /root/app
- type: bind
source: ${HOME}
source: '~'
target: /root
build:
context: .
Expand Down
229 changes: 177 additions & 52 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@

import os
import sqlite3
from sqlite3 import IntegrityError
from typing import Dict, List, Generator, Tuple
from utils.fs import genHash, isImg, imgPaths, homeDir, detectFileWithHash, deleteFile, pathExist, pathOf
from utils.db import insertIntoDB, deleteByClass, groupByClass, toggleVisibility, connectDB, closeConnection, cleanDB, hashExist
from utils.createDB import createSchema, classesExist
from yolov8 import detectClasses
from flask import Flask, render_template, send_file, request, redirect, url_for
from typing import Dict, List
from utils import *
from media import *
from flask import Flask, render_template, send_file, request, redirect, url_for, Response, jsonify
from markupsafe import escape
from urllib.parse import unquote

writing = False

def dataDir() -> str:
"""
Data directory is created on the user's home directory.
Returns:
str: The path to the home directory.
"""
directory = os.path.join(os.path.expanduser("~"), ".pictopy")
os.makedirs(directory, exist_ok=True)
return directory

def dbPath() -> str:
"""
Expand All @@ -18,86 +26,203 @@ def dbPath() -> str:
Returns:
str: The path to the database file.
"""
return os.path.join(os.path.expanduser("~"), ".pictopy.db")
return os.path.join(dataDir(), "database.db")

def logPath() -> str:
"""
Log file is created on the user's home directory.
def processImgs(conn: sqlite3.Connection, files: Generator[str, None, None]) -> None:
Returns:
str: The path to the log file.
"""
Processes images by extracting their hash values, detecting their classes, and storing them in the database.
return os.path.join(dataDir(), "log.txt")

def updateDB(groupBy: str = None) -> None:
"""
Updates the database schema and populates the media table.
Populates the media table with paths from the home directory.
Optionally classifies media by class if specified.
Cleans the database.
Args:
conn: The database connection object.
files: A generator of file paths.
groupBy (str, optional): Specifies whether to classify media by 'class'. Defaults to None.
"""

objDetectionModel = pathOf("models/yolov8n.onnx")
for file in files:
imgHash = genHash(file)
if hashExist(conn, imgHash):
continue
try:
imgClass = detectClasses(file, objDetectionModel)
except Exception as e:
print(e)
continue
insertIntoDB(conn, file, imgClass, imgHash)

def classifyPath() -> Dict[str, Tuple[str]]:
writeConn = connectDB(dbPath())
createSchema(writeConn,
{
"MEDIA": [
"mediaID INTEGER PRIMARY KEY AUTOINCREMENT",
"hash TEXT UNIQUE",
"path TEXT",
"directory TEXT",
"fileType TEXT CHECK(fileType IN ('img', 'vid'))",
"timeStamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
"hidden INTEGER"
],
"CLASS": [
"classID INTEGER PRIMARY KEY AUTOINCREMENT",
"class TEXT UNIQUE"
],
"JUNCTION": [
"mediaID INTEGER",
"classID INTEGER",
"FOREIGN KEY(mediaID) REFERENCES MEDIA(mediaID) ON DELETE CASCADE",
"FOREIGN KEY(classID) REFERENCES CLASS(classID)",
"PRIMARY KEY (mediaID, classID)"
]
}
)

populateMediaTable(writeConn, mediaPaths(homeDir()))
if groupBy == "class":
classifyMedia(writeConn, pathOf("models/yolov8n.onnx"), getUnlinkedMedia(connectDB(dbPath())))
cleanDB(writeConn)
closeConnection(writeConn)

def groupPaths(hidden, fileType, groupBy) -> str:
"""
Classify images in the home directory and store the results in the database.
Groups media paths by directory or class and returns them as JSON.
Args:
hidden (int): Specifies whether to include hidden files.
fileType (str): Specifies the type of files to include ('img' or 'vid').
groupBy (str): Specifies the grouping method ('directory' or 'class').
Returns:
Dict[str, Tuple[str]]: Dictionary mapping class names to lists of file paths.
str: JSON created from a list of tuples where each tuple contains a group name and a group of paths.
"""
conn = connectDB(dbPath())
createSchema(conn)
global writing
if not writing:
writing = True
updateDB(groupBy)
writing = False

readConn = connectDB(dbPath())
if groupBy == "directory":
result = groupByDir(readConn, hidden, fileType)
else:
result = groupByClass(readConn, hidden, fileType)
closeConnection(readConn)

processImgs(conn, imgPaths(homeDir()))
return jsonify(result)

# Clear unavailable paths from DB
cleanDB(conn)
def decodeLinkPath(path: str) -> str:
"""
Decodes a URL-encoded path and attempts to find the corresponding file or directory.
If the file or directory exists under either the Unix-style path or the relative path,
it returns the absolute path. Otherwise, it should redirect to the index page.
Args:
path: The URL-encoded path to decode and locate.
result = groupByClass(conn)
Returns:
The absolute path if found, or a redirect to the index page if not.
"""
path = escape(unquote(path))

closeConnection(conn)
# Convert the path to Windows-style path for checking
unixPath = f"/{path}"
if pathExist(unixPath):
return unixPath

return result
# Convert the path to Windows-style path for checking
windowsPath = path.replace('/', '\\')
if pathExist(windowsPath):
return windowsPath

# periodically run the object detection function and compare it with DB (TBI)
return redirect(url_for('index')) # doesn't reload (TBI)

app = Flask(__name__, template_folder=f"{pathOf('static')}")

@app.route('/')
def index():
return render_template('index.html', classDict=classifyPath())
return render_template('index.html')

@app.route('/static/<path:path>')
def static_file(path):
def staticFile(path):
return app.send_static_file(pathOf(path))

@app.route('/media/<path:path>')
def media(path):
path = escape(f"/{path}")
if pathExist(path):
return send_file(path)
return redirect(url_for('index')) # doesn't reload / (TBI)
def sendFile(path):
return send_file(decodeLinkPath(path))

@app.route('/thumbnail/<path:path>')
def thumbnail(path):
return getThumbnail(decodeLinkPath(path))

# Sections

@app.route('/<string:fileType>/<string:groupBy>')
def groupMedia(fileType, groupBy):
if fileType not in ["img", "vid"] or groupBy not in ["class", "directory"]:
return redirect(url_for('index'))
return groupPaths(0, fileType, groupBy)

@app.route('/hidden/<string:groupBy>')
def hidden(groupBy):
if groupBy not in ["class", "directory"]:
return redirect(url_for('index'))
return groupPaths(1, "any", groupBy)

@app.route('/trash/<string:groupBy>')
def trash(groupBy):
if groupBy not in ["class", "directory"]:
return redirect(url_for('index'))
return groupPaths(-1, "any", groupBy)

# Buttons

@app.route('/toTrash', methods=['POST'])
def toTrash():
data = request.get_json().get('selectedMedia', [])
print(f"Moving files to trash: {data}")
conn = connectDB(dbPath())
moveToTrash(conn, data)
closeConnection(conn)
return jsonify({"success": True})

@app.route('/delete', methods=['POST'])
def delete():
data = tuple(request.get_json().get('selectedImages', []))
print(f"Deleting images: {data}")
data = request.get_json().get('selectedMedia', [])
print(f"Deleting files: {data}")
conn = connectDB(dbPath())
deleteFromDB(conn, data)
closeConnection(conn)
return redirect(url_for('index'))
return jsonify({"success": True})

@app.route('/hide', methods=['POST'])
def hide():
data = tuple(request.get_json().get('selectedImages', []))
print(f"Hiding images: {data}")
data = request.get_json().get('selectedMedia', [])
print(f"Hiding files: {data}")
conn = connectDB(dbPath())
toggleVisibility(conn, data, 1)
closeConnection(conn)
return redirect(url_for('index'))
return jsonify({"success": True})

@app.route('/unhide', methods=['POST'])
def unhide():
data = request.get_json().get('selectedMedia', [])
print(f"Unhiding files: {data}")
conn = connectDB(dbPath())
toggleVisibility(conn, data, 0)
closeConnection(conn)
return jsonify({"success": True})

@app.route('/restore', methods=['POST'])
def restore():
data = request.get_json().get('selectedMedia', [])
print(f"Restoring files: {data}")
conn = connectDB(dbPath())
toggleVisibility(conn, data, 0)
closeConnection(conn)
return jsonify({"success": True})

@app.route('/info/<path:path>')
def info(path):
conn = connectDB(dbPath())
info = getInfoByPath(conn, decodeLinkPath(path))
closeConnection(conn)
return jsonify(info)

if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
3 changes: 3 additions & 0 deletions media/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .image import imageClasses
from .video import videoClasses, getThumbnail
from .process import populateMediaTable, classifyMedia
Loading

0 comments on commit 39a4efe

Please sign in to comment.