Added the relevant source files.
This commit is contained in:
310
capture.c
Normal file
310
capture.c
Normal 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
272
play
Normal 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)
|
||||
Reference in New Issue
Block a user