Added the relevant source files.

This commit is contained in:
Markil3
2021-08-07 16:48:54 -06:00
committed by GitHub
parent fbade9b080
commit 70fb2a2765
2 changed files with 582 additions and 0 deletions

310
capture.c Normal file
View File

@@ -0,0 +1,310 @@
#include <linux/input.h>
#include <linux/input-event-codes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/wait.h>
int callSound(char* path)
{
int child = fork();
if (child == 0)
{
char finalPath[60];
strcpy(finalPath, getenv("HOME"));
strcat(finalPath, "/.local/share/sounds/");
strcat(finalPath, path);
execlp("ffmpeg", "ffmpeg", "-loglevel", "error", "-i", finalPath, "-f", "alsa", "default", NULL);
}
else if (child < 0)
{
printf("Could not generate sound %s\n", path);
exit(5);
}
else
{
return child;
}
}
void callErrorSound(int code)
{
int returnCode = callSound("error.wav");
wait(NULL);
for (int i = 0; i < code; i++)
{
returnCode = callSound("bit.wav");
wait(NULL);
}
}
int main(int argc, char **argv)
{
const int NUM_KEYS = 249;
const int ARG_LENGTH = 20;
const int MAX_ARGS = 20;
char validChars[NUM_KEYS][2];
for (int i = 0; i < NUM_KEYS; i++)
{
switch (i)
{
case KEY_0:
validChars[i][0] = '0';
validChars[i][1] = '0';
break;
case KEY_1:
validChars[i][0] = '1';
validChars[i][1] = '1';
break;
case KEY_2:
validChars[i][0] = '2';
validChars[i][1] = '2';
break;
case KEY_3:
validChars[i][0] = '3';
validChars[i][1] = '3';
break;
case KEY_4:
validChars[i][0] = '4';
validChars[i][1] = '4';
break;
case KEY_5:
validChars[i][0] = '5';
validChars[i][1] = '5';
break;
case KEY_6:
validChars[i][0] = '6';
validChars[i][1] = '6';
break;
case KEY_7:
validChars[i][0] = '7';
validChars[i][1] = '7';
break;
case KEY_8:
validChars[i][0] = '8';
validChars[i][1] = '8';
break;
case KEY_9:
validChars[i][0] = '9';
validChars[i][1] = '9';
break;
case KEY_A:
validChars[i][0] = 'A';
validChars[i][1] = 'a';
break;
case KEY_B:
validChars[i][0] = 'B';
validChars[i][1] = 'b';
break;
case KEY_C:
validChars[i][0] = 'C';
validChars[i][1] = 'c';
break;
case KEY_D:
validChars[i][0] = 'D';
validChars[i][1] = 'd';
break;
case KEY_E:
validChars[i][0] = 'E';
validChars[i][1] = 'e';
break;
case KEY_F:
validChars[i][0] = 'F';
validChars[i][1] = 'f';
break;
case KEY_G:
validChars[i][0] = 'G';
validChars[i][1] = 'g';
break;
case KEY_H:
validChars[i][0] = 'H';
validChars[i][1] = 'h';
break;
case KEY_I:
validChars[i][0] = 'I';
validChars[i][1] = 'i';
break;
case KEY_J:
validChars[i][0] = 'J';
validChars[i][1] = 'j';
break;
case KEY_K:
validChars[i][0] = 'K';
validChars[i][1] = 'k';
break;
case KEY_L:
validChars[i][0] = 'L';
validChars[i][1] = 'l';
break;
case KEY_M:
validChars[i][0] = 'M';
validChars[i][1] = 'm';
break;
case KEY_N:
validChars[i][0] = 'N';
validChars[i][1] = 'n';
break;
case KEY_O:
validChars[i][0] = 'O';
validChars[i][1] = 'o';
break;
case KEY_P:
validChars[i][0] = 'P';
validChars[i][1] = 'p';
break;
case KEY_Q:
validChars[i][0] = 'Q';
validChars[i][1] = 'q';
break;
case KEY_R:
validChars[i][0] = 'R';
validChars[i][1] = 'r';
break;
case KEY_S:
validChars[i][0] = 'S';
validChars[i][1] = 's';
break;
case KEY_T:
validChars[i][0] = 'T';
validChars[i][1] = 't';
break;
case KEY_U:
validChars[i][0] = 'U';
validChars[i][1] = 'u';
break;
case KEY_V:
validChars[i][0] = 'V';
validChars[i][1] = 'v';
break;
case KEY_W:
validChars[i][0] = 'W';
validChars[i][1] = 'w';
break;
case KEY_X:
validChars[i][0] = 'X';
validChars[i][1] = 'x';
break;
case KEY_Y:
validChars[i][0] = 'Y';
validChars[i][1] = 'y';
break;
case KEY_Z:
validChars[i][0] = 'Z';
validChars[i][1] = 'z';
break;
case KEY_MINUS:
validChars[i][0] = '_';
validChars[i][1] = '-';
break;
default:
validChars[i][0] = 0;
validChars[i][1] = 1;
break;
}
}
/*
* We need this command for when we trigger the app from the command line,
* because otherwise the upstroke on the enter key will kill the app.
*/
int waitEnter = 0;
char* ARGS[MAX_ARGS];
int numArgs = 0;
ARGS[numArgs++] = "play";
//ARGS[numArgs++] = "--directory=/home/markil3/.local/share/lollypop";
char* BUFFER = (char*) calloc(ARG_LENGTH, sizeof(char));
int index = 0;
BUFFER[index] = '\0';
int fd;
int len;
if(argc < 2) {
printf("usage: %s <device>\n", argv[0]);
callErrorSound(1);
return 1;
}
fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
callErrorSound(2);
exit(2);
}
struct input_event ev;
callSound("triggerActivate.wav");
while (len >= 0 && ev.value != 0 || waitEnter == 0 || (ev.code != KEY_ENTER && ev.code != KEY_ESC))
{
len = read(fd, &ev, sizeof(struct input_event));
if (len < 0)
{
break;
}
if ((ev.code == KEY_ENTER || ev.code == KEY_ESC) && ev.value == 1)
{
waitEnter = 1;
}
if (ev.type == 1 && ev.value == 1)
{
//printf("Keycode: %i, status %i\n", ev.code, ev.value);
if ((ev.code == KEY_SPACE || ev.code == KEY_ENTER) && index > 0)
{
if (ev.code == KEY_SPACE)
{
callSound("bit.wav");
}
BUFFER[index] = '\0';
ARGS[numArgs++] = BUFFER;
BUFFER = (char*) calloc(ARG_LENGTH, sizeof(char));
index = 0;
}
else if (ev.code < NUM_KEYS && validChars[ev.code][1] != 0)
{
if (index < ARG_LENGTH)
{
BUFFER[index++] = validChars[ev.code][1];
BUFFER[index] = '\0';
callSound("bit.wav");
}
}
}
}
close(fd);
ARGS[numArgs] = NULL;
if (len < 0)
{
callErrorSound(3);
exit(3);
}
if (index > 0)
{
/*
* If index was not reset, then we canceled.
*/
callSound("end.wav");
exit(0);
}
callSound("triggerDeactivate.wav");
printf("Running args: ");
for (int i = 0; i <= numArgs; i++)
{
printf("'%s' ", ARGS[i]);
}
printf("\n");
execvp(ARGS[0], ARGS);
}

272
play Normal file
View File

@@ -0,0 +1,272 @@
#!/usr/bin/env python
import pathlib
from pathlib import Path
from urllib import parse
import subprocess
import sys
import random
from os.path import exists
import sqlite3
'''
A sorting method for songs. It works by pushing file names with more matches up higher.
'''
def getValueFunction(terms):
def valOfResult(song):
val = 0
for arg in terms:
for part in song[2:]:
if arg in part.lower():
val -= 1
# Double every point received by a playlist
if song[0] == True:
val -= 1
#print("%s got %d hits" % (song, val))
return val
return valOfResult
'''
Merges certain words into one. This is helpful for voice commands
'''
def mergeWords(argList):
i = 0
quoting = False
while i < len(argList):
# Merge underscore for handling later
if quoting:
if argList[i] != "unquote":
argList[i - 1] = argList[i - 1] + " " + argList[i]
else:
quoting = False
del argList[i]
i -= 1
elif argList[i] == "quote":
quoting = True
del argList[i]
elif i < len(argList) - 1 and argList[i] == "under" and argList[i + 1] == "score":
argList[i] = "underscore"
del argList[i + 1]
elif i < len(argList) - 1 and argList[i] == "play" and argList[i + 1] == "list":
argList[i] = "playlist"
del argList[i + 1]
if argList[i] == "dot":
argList[i - 1] = argList[i - 1] + "."
del argList[i]
i -= 1
elif argList[i] == "underscore":
argList[i - 1] = argList[i - 1] + "_"
del argList[i]
i -= 1
elif i < len(argList) - 1 and argList[i] == "dash":
argList[i] = "-" + argList[i + 1]
del argList[i + 1]
elif argList[i - 1].endswith(".") or argList[i - 1].endswith("_"):
argList[i - 1] = argList[i - 1] + argList[i]
del argList[i]
i -= 1
i += 1
return argList
def extractOptions(args):
options = {}
i = 0
while i < len(args):
# Some dashed fields have special meanings.
if args[i].startswith("-") and args[i] != "-or" and args[i] != "-and" and args[i] != "--or" and args[i] != "--and":
if args[i].startswith("--"):
if "=" in args[i]:
options[args[i][2:args[i].index("=")]] = args[i][args[i].index("=") + 1:]
else:
options[args[i][2:]] = True
# These two have special meanings
else:
for char in args[i][1:]:
options[char] = True
del args[i]
elif args[i] == "playlist" or args[i] == "m3u":
options["playlist"] = True
del args[i]
elif args[i].endswith(".m3u"):
options["playlist"] = True
args[i] = args[i][0:args[i].rindex(".m3u")]
i += 1
else:
i += 1
return options
def searchFiles(terms, options):
musicHome = Path(Path.home(), "Music")
if not musicHome.is_dir():
# We are probably on Windows
musicHome = Path(Path.home(), "My Music")
findArgs = ["find", str(musicHome)]
terms = appendSearchArguments(findArgs, sys.argv[1:], options)
print(" ".join(findArgs))
print()
process = subprocess.Popen(findArgs, stdout=subprocess.PIPE)
output, error = process.communicate()
files = output.decode().split("\n")
# Remove any empty things
while "" in files:
files.remove("")
return files
'''
Compiles all arguments in the argument list into a SQLite search term
@return A set of search terms provided, stripped of all extra arguments.
'''
def appendDatabaseArguments(argList, args, options, playlist):
notArgs = []
terms = set()
if len(args) > 0:
for arg in args:
if arg.startswith("!"):
notArgs.append(arg[1:])
elif arg == "AND" or arg == "and" or arg == "-and" or arg =="--and" or arg == "&&":
# Override whatever AND/OR we had before
argList.pop()
argList.append("AND")
elif arg == "OR" or arg == "or" or arg == "-or" or arg =="--or" or arg == "||":
# Override whatever AND/OR we had before
argList.pop()
argList.append("OR")
else:
argList.append("(")
if playlist:
argList.append("playlists.name")
argList.append("LIKE")
argList.append("'%" + arg + "%'")
else:
argList.append("tracks.name")
argList.append("LIKE")
argList.append("'%" + arg + "%'")
argList.append("OR")
argList.append("albums.name")
argList.append("LIKE")
argList.append("'%" + arg + "%'")
argList.append("OR")
argList.append("artists.name")
argList.append("LIKE")
argList.append("'%" + arg + "%'")
argList.append(")")
terms.add(arg)
if "a" in options:
argList.append("AND")
else:
argList.append("OR")
if argList[len(argList) - 1] == "AND" or argList[len(argList) - 1] == "OR":
argList.pop()
if len(notArgs) > 0:
argList.append("AND")
for arg in notArgs:
argList.append("tracks.name")
argList.append("NOT")
argList.append("LIKE")
argList.append("'%" + arg + "%'")
argList.append("AND")
argList.pop()
return terms
def searchDatabase(options):
files = []
if "directory" in options:
dbDir = Path(options["directory"])
else:
dbDir = Path(Path.home(), ".local/share/lollypop")
if "playlist" in options:
db = sqlite3.connect(str(Path(dbDir, "playlists.db")))
findArgs = "SELECT tracks.uri, playlists.name FROM tracks INNER JOIN playlists ON tracks.playlist_id = playlists.id WHERE".split(" ")
terms = appendDatabaseArguments(findArgs, sys.argv[1:], options, True)
findArgs = " ".join(findArgs) + ";"
results = db.execute(findArgs);
for row in results:
files.append((True,) + row)
db.close()
db = sqlite3.connect(str(Path(dbDir, "lollypop.db")))
findArgs = "SELECT tracks.uri, tracks.name, albums.name, artists.name FROM tracks INNER JOIN albums ON tracks.album_id = albums.id INNER JOIN track_artists ON tracks.id = track_artists.track_id INNER JOIN artists ON track_artists.artist_id = artists.id WHERE".split(" ")
terms = appendDatabaseArguments(findArgs, sys.argv[1:], options, False)
findArgs = " ".join(findArgs) + ";"
results = db.execute(findArgs);
for row in results:
files.append((False,) + row)
db.close()
return (files, terms)
if __name__ == "__main__":
# Checks if VLC is installed
missing = subprocess.call("command -v vlc >> /dev/null", shell=True)
if missing:
print("VLC Media Player is not installed", file=sys.stderr)
exit(1)
print(sys.argv)
mergeWords(sys.argv)
options = extractOptions(sys.argv)
if "h" in options or "help" in options:
print("""Usage: %s [OPTIONS]... [TERM]...
Searches for a song within the music collection
OPTIONS:
--directory=[dir] Define the database directory.
-a The search must match every single term
-v Displays the media player
-l Loops through the found files until a new command is entered.
-s Only the first option will play.
-h, --help Shows this message
TERMS:
For the most part, you can just enter a single-word term, and it will be added
to the possible substrings included in the file name search. However, there are
a few additional ways to refine your search.
-and, --and, AND, and Any of these keywords will cause the previous and next
terms to be linked together, so that they MUST appear
for the file to qualify.
-or, --or, OR, or Any of these keywords will cause the previous and next
terms to be linked together, so that only one or the
other needs to appear for the file to qualify.
Overrides the -a option for those two terms.
! Add this to the beginning of a term to ensure that the
keyword does NOT appear in any matching file paths.
m3u, playlist Normally, playlists are ignored during the file search.
However, this term will not only allow for their
search, but will also give them higher precedence.""" % (sys.argv[0]))
exit(0)
files, terms = searchDatabase(options)
if len(files) == 0:
print("No files found", file=sys.stderr)
exit(1)
random.shuffle(files)
files.sort(key=getValueFunction(terms))
for i in range(len(files)):
files[i] = files[i][1]
if "s" in options:
files = [files[0]]
for song in files:
print(song)
if "v" in options:
vlcArgs = ["vlc"]
else:
vlcArgs = ["cvlc"]
if "l" in options:
vlcArgs.append("--loop")
else:
files.append("vlc://quit")
vlcArgs += files
#print(" ".join(vlcArgs))
print("Launching VLC: %s" % (vlcArgs))
process = subprocess.Popen(vlcArgs, stdout=sys.stdout, stderr=sys.stderr)
if not "l" in options:
exit(process.wait())
else:
exit(0)