This commit is contained in:
√(noham)²
2024-03-03 15:52:50 +01:00
parent 5c5d9514ce
commit 828e481d1d
12 changed files with 353 additions and 37 deletions

129
.gitignore vendored
View File

@@ -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

View File

@@ -1,2 +1,6 @@
![music player](Music-Player.gif) ![music player](Music-Player.gif)
![diagramme](Diagramme.drawio.png) ![diagramme](Diagramme.drawio.png)
# To do :
- SHA-256
- not playing state (js)

View File

@@ -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"]

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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
View 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>

View File

@@ -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
View 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