mirror of
https://github.com/NohamR/AM-Exporter.git
synced 2026-05-25 04:07:11 +00:00
changes
This commit is contained in:
129
.gitignore
vendored
129
.gitignore
vendored
@@ -1,3 +1,132 @@
|
|||||||
|
# Editors
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Vagrant
|
||||||
|
.vagrant/
|
||||||
|
|
||||||
|
# Mac/OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Source for the following rules: https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
dist_chrome/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# node
|
||||||
|
node_modules/
|
||||||
|
|
||||||
back/.users
|
back/.users
|
||||||
user/.env
|
user/.env
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
# To do :
|
||||||
|
- SHA-256
|
||||||
|
- not playing state (js)
|
||||||
@@ -10,4 +10,4 @@ EXPOSE 3005
|
|||||||
|
|
||||||
RUN pip install gunicorn
|
RUN pip install gunicorn
|
||||||
|
|
||||||
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:3005", "--chdir", "/t", "app:app"]
|
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:3005", "--chdir", "/t", "--log-level", "debug", "app:app"]
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
import json
|
import json
|
||||||
#import hashlib
|
import hashlib
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app, resources={r"/music/*": {"origins": "http://*"}})
|
CORS(app, resources={r"/music/*": {"origins": "http://*"}})
|
||||||
@@ -16,12 +16,12 @@ def set_content():
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
user = data.get('user')
|
user = data.get('user')
|
||||||
password = data.get('password')
|
password = data.get('password')
|
||||||
if data['user'] in users and users[data['user']] == data['password']:
|
if user in users and users[user] == hashlib.sha256(password.encode()).hexdigest():
|
||||||
# if user in users and users[user] == hashlib.sha256(password.encode()).hexdigest():
|
# cache.clear()
|
||||||
cache.update(data)
|
cache.update(data)
|
||||||
cache.pop('user', None)
|
cache.pop('user', None)
|
||||||
cache.pop('password', None)
|
cache.pop('password', None)
|
||||||
return jsonify({'message': 'Content set successfully.'})
|
return jsonify({'message': f'Content set successfully.'})
|
||||||
else:
|
else:
|
||||||
return jsonify({'message': 'Invalid user or password.'}), 401
|
return jsonify({'message': 'Invalid user or password.'}), 401
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ function secondsToMinutesAndSeconds(seconds) {
|
|||||||
return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
|
return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
|
||||||
}
|
}
|
||||||
function fetchDataAndAnimate() {
|
function fetchDataAndAnimate() {
|
||||||
fetch('http://127.0.0.1:5000/music/get')
|
// fetch('http://192.168.1.58:3005/music/get')
|
||||||
|
fetch('http://192.168.1.64:3005/music/get')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const artist = data.artist;
|
const artist = data.artist;
|
||||||
@@ -18,6 +19,7 @@ function fetchDataAndAnimate() {
|
|||||||
const decalage = (Date.now() / 1000 - timestamp);
|
const decalage = (Date.now() / 1000 - timestamp);
|
||||||
let pPosition = Math.round(parseFloat(data.pPosition) + decalage - 3);
|
let pPosition = Math.round(parseFloat(data.pPosition) + decalage - 3);
|
||||||
const duration = parseFloat(data.duration);
|
const duration = parseFloat(data.duration);
|
||||||
|
const status = data.status;
|
||||||
|
|
||||||
const titleSongElement = document.querySelector('.title-song');
|
const titleSongElement = document.querySelector('.title-song');
|
||||||
// titleSongElement.textContent = name;
|
// titleSongElement.textContent = name;
|
||||||
@@ -40,6 +42,7 @@ function fetchDataAndAnimate() {
|
|||||||
element.href = itunes_url;
|
element.href = itunes_url;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (status === 'playing') {
|
||||||
const totaltimeElement = document.querySelector('.total-time');
|
const totaltimeElement = document.querySelector('.total-time');
|
||||||
totaltimeElement.textContent = time;
|
totaltimeElement.textContent = time;
|
||||||
|
|
||||||
@@ -72,6 +75,22 @@ function fetchDataAndAnimate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
intervalId = setInterval(updateProgress, 1000);
|
intervalId = setInterval(updateProgress, 1000);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const totaltimeElement = document.querySelector('.total-time');
|
||||||
|
totaltimeElement.textContent = time;
|
||||||
|
|
||||||
|
const lasttimeElement = document.querySelector('.last-time');
|
||||||
|
lasttimeElement.textContent = time;
|
||||||
|
|
||||||
|
const rapport = (pPosition / duration) * 100;
|
||||||
|
const trackElements = document.querySelectorAll('.track');
|
||||||
|
trackElements.forEach(element => {
|
||||||
|
const style = window.getComputedStyle(element, '::after');
|
||||||
|
const currentWidth = parseFloat(style.getPropertyValue('width'));
|
||||||
|
element.style.setProperty('--new-width', `100%`);
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error fetching data:', error);
|
console.error('Error fetching data:', error);
|
||||||
|
|||||||
16
user/export.applescript
Normal file
16
user/export.applescript
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
tell application "Music"
|
||||||
|
if it is running then
|
||||||
|
if player state is playing then
|
||||||
|
set pState to player state
|
||||||
|
set pPosition to player position
|
||||||
|
set cTrack to current track
|
||||||
|
|
||||||
|
set trackInfo to "{\"status\": \"playing\", \"persistent ID\": \"" & persistent ID of cTrack & "\", \"name\": \"" & name of cTrack & "\", \"time\": \"" & time of cTrack & "\", \"duration\": \"" & duration of cTrack & "\", \"artist\": \"" & artist of cTrack & "\", \"album artist\": \"" & album artist of cTrack & "\", \"composer\": \"" & composer of cTrack & "\", \"album\": \"" & album of cTrack & "\", \"genre\": \"" & genre of cTrack & "\", \"played count\": \"" & played count of cTrack & "\", \"pState\": \"" & pState & "\", \"pPosition\": \"" & pPosition & "\" }"
|
||||||
|
return trackInfo
|
||||||
|
else
|
||||||
|
return "{\"status\": \"not playing\"}"
|
||||||
|
end if
|
||||||
|
else
|
||||||
|
return "{\"status\": \"not running\"}"
|
||||||
|
end if
|
||||||
|
end tell
|
||||||
104
user/export.py
Normal file
104
user/export.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from pprint import pprint
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
USER = os.getenv("USER")
|
||||||
|
PASSWORD = os.getenv("PASSWORD")
|
||||||
|
|
||||||
|
stdout_file = 'logfile.log'
|
||||||
|
stderr_file = 'error_logfile.log'
|
||||||
|
|
||||||
|
sys.stdout = open(stdout_file, 'a')
|
||||||
|
sys.stderr = open(stderr_file, 'a')
|
||||||
|
|
||||||
|
def printout(content):
|
||||||
|
print(f"{datetime.now().strftime('%H:%M:%S')} : {content}", file=sys.stdout)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def printerr(content):
|
||||||
|
print(f"{datetime.now().strftime('%H:%M:%S')} : An error occurred: {str(content)}", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def get_current_song():
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(['osascript', 'export.applescript']).decode('utf-8').strip()
|
||||||
|
return output
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
printerr(e)
|
||||||
|
return e
|
||||||
|
|
||||||
|
def get_track_extras(song, artist, album):
|
||||||
|
query = f"{song} {artist} {album}"
|
||||||
|
params = {"media": "music", "entity": "song", "term": query}
|
||||||
|
|
||||||
|
r = requests.get("https://itunes.apple.com/search", params=params)
|
||||||
|
json_data = r.json()
|
||||||
|
if json_data["resultCount"] == 1:
|
||||||
|
result = json_data["results"][0]
|
||||||
|
elif json_data["resultCount"] > 1:
|
||||||
|
result = json_data["results"][0]
|
||||||
|
else :
|
||||||
|
result = ''
|
||||||
|
|
||||||
|
artwork_url = result["artworkUrl100"] if result else None
|
||||||
|
itunes_url = result["trackViewUrl"] if result else None
|
||||||
|
artist_url = result["artistViewUrl"] if result else None
|
||||||
|
|
||||||
|
return (artwork_url, itunes_url, artist_url)
|
||||||
|
|
||||||
|
def post(currentsong):
|
||||||
|
currentsong['user'] = USER
|
||||||
|
currentsong['password'] = PASSWORD
|
||||||
|
data = json.dumps(currentsong)
|
||||||
|
try:
|
||||||
|
r = requests.post(url+'/music/set', data=data, headers=headers)
|
||||||
|
if r.status_code != 200:
|
||||||
|
return r.status_code
|
||||||
|
else :
|
||||||
|
return r.text
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {str(e)}", file=sys.stderr)
|
||||||
|
|
||||||
|
url = "https://api.noh.am" #RUN WEB
|
||||||
|
url = "http://127.0.0.1:5000" #DEV
|
||||||
|
url = "http://0.0.0.0:3005"
|
||||||
|
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
persistendId = ''
|
||||||
|
prevstatus = ''
|
||||||
|
while True:
|
||||||
|
# print('getting data..', file=sys.stdout)
|
||||||
|
# currentsong = json.loads(str(get_current_song()).replace("'''", '"'))
|
||||||
|
currentsong = json.loads(get_current_song())
|
||||||
|
|
||||||
|
if currentsong['status'] == 'playing':
|
||||||
|
if currentsong['persistent ID'] != persistendId:
|
||||||
|
persistendId = currentsong['persistent ID']
|
||||||
|
currentsong['timestamp'] = time.time()
|
||||||
|
(currentsong['artwork_url'], currentsong['itunes_url'], currentsong['artist_url']) = get_track_extras(currentsong['name'], currentsong['artist'], currentsong['album'])
|
||||||
|
printout(f"{post(currentsong)}")
|
||||||
|
timets = float(currentsong['duration'].replace(",", "."))-float(currentsong['pPosition'].replace(",", ".")) + 3
|
||||||
|
prevstatus = 'playing'
|
||||||
|
elif currentsong['status'] == 'not playing' and prevstatus != 'not playing':
|
||||||
|
prevstatus = 'not playing'
|
||||||
|
printout(f"{post({'status' : 'not playing'})}")
|
||||||
|
timets = 5*60
|
||||||
|
elif currentsong['status'] == 'not running' and prevstatus != 'not running':
|
||||||
|
prevstatus = 'not running'
|
||||||
|
printout(f"{post({'status' : 'not running'})}")
|
||||||
|
timets = 5*60
|
||||||
|
else:
|
||||||
|
timets = 5*60
|
||||||
|
|
||||||
|
time.sleep(timets)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
9
user/install.sh
Executable file
9
user/install.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh -xe
|
||||||
|
|
||||||
|
echo --- Copy launch agent plist
|
||||||
|
mkdir ~/Library/LaunchAgents/ || true
|
||||||
|
cp -f music-exp.plist ~/Library/LaunchAgents/
|
||||||
|
|
||||||
|
echo --- Load launch agent
|
||||||
|
launchctl load ~/Library/LaunchAgents/music-exp.plist
|
||||||
|
echo --- INSTALL SUCCESS
|
||||||
24
user/music-exp.plist
Normal file
24
user/music-exp.plist
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>am.noh.music-exp</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<!-- <string>/usr/bin/python3</string> -->
|
||||||
|
<string>/Users/noham/miniconda3/envs/310/bin/python</string>
|
||||||
|
<string>export.py</string>
|
||||||
|
</array>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>logfile.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>error_logfile.log</string>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>/Users/noham/Documents/GitHub/AppleMusicApi/user</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
15
user/test.py
15
user/test.py
@@ -10,7 +10,11 @@ USER = os.getenv("USER")
|
|||||||
PASSWORD = os.getenv("PASSWORD")
|
PASSWORD = os.getenv("PASSWORD")
|
||||||
|
|
||||||
def get_current_song():
|
def get_current_song():
|
||||||
return subprocess.check_output(['osascript', 'test.applescript']).decode('utf-8').strip()
|
try:
|
||||||
|
output = subprocess.check_output(['osascript', 'export.applescript']).decode('utf-8').strip()
|
||||||
|
return output
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return e
|
||||||
|
|
||||||
def get_track_extras(song, artist, album):
|
def get_track_extras(song, artist, album):
|
||||||
query = f"{song} {artist} {album}"
|
query = f"{song} {artist} {album}"
|
||||||
@@ -20,7 +24,6 @@ def get_track_extras(song, artist, album):
|
|||||||
json_data = r.json()
|
json_data = r.json()
|
||||||
if json_data["resultCount"] == 1:
|
if json_data["resultCount"] == 1:
|
||||||
result = json_data["results"][0]
|
result = json_data["results"][0]
|
||||||
pprint(result)
|
|
||||||
elif json_data["resultCount"] > 1:
|
elif json_data["resultCount"] > 1:
|
||||||
result = json_data["results"][0]
|
result = json_data["results"][0]
|
||||||
else :
|
else :
|
||||||
@@ -29,7 +32,6 @@ def get_track_extras(song, artist, album):
|
|||||||
artwork_url = result["artworkUrl100"] if result else None
|
artwork_url = result["artworkUrl100"] if result else None
|
||||||
itunes_url = result["trackViewUrl"] if result else None
|
itunes_url = result["trackViewUrl"] if result else None
|
||||||
artist_url = result["artistViewUrl"] if result else None
|
artist_url = result["artistViewUrl"] if result else None
|
||||||
# album_url = result["collectionViewUrl"] if result else None
|
|
||||||
|
|
||||||
return (artwork_url, itunes_url, artist_url)
|
return (artwork_url, itunes_url, artist_url)
|
||||||
|
|
||||||
@@ -43,8 +45,10 @@ def post(currentsong):
|
|||||||
else :
|
else :
|
||||||
return r.text
|
return r.text
|
||||||
|
|
||||||
url = "http://192.168.1.58:3005" #RUN
|
url = "https://api.noh.am" #RUN WEB
|
||||||
url = "http://127.0.0.1:5000" #DEV
|
url = "http://127.0.0.1:5000" #DEV
|
||||||
|
url = "http://0.0.0.0:3005"
|
||||||
|
|
||||||
headers = {'Content-Type': 'application/json'}
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -52,7 +56,8 @@ def main():
|
|||||||
prevstatus = ''
|
prevstatus = ''
|
||||||
while True:
|
while True:
|
||||||
print('getting data..')
|
print('getting data..')
|
||||||
currentsong = json.loads(str(get_current_song()).replace("'''", '"'))
|
# currentsong = json.loads(str(get_current_song()).replace("'''", '"'))
|
||||||
|
currentsong = json.loads(get_current_song())
|
||||||
|
|
||||||
if currentsong['status'] == 'playing':
|
if currentsong['status'] == 'playing':
|
||||||
if currentsong['persistent ID'] != persistendId:
|
if currentsong['persistent ID'] != persistendId:
|
||||||
|
|||||||
6
user/uninstall.sh
Executable file
6
user/uninstall.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh -xe
|
||||||
|
echo --- Unload launch agent
|
||||||
|
launchctl unload ~/Library/LaunchAgents/music-exp.plist
|
||||||
|
echo --- Remove launch agent plist
|
||||||
|
rm -f ~/Library/LaunchAgents/music-exp.plist || true
|
||||||
|
echo --- UNINSTALL SUCCESS
|
||||||
Reference in New Issue
Block a user