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
user/.env

View File

@@ -1,2 +1,6 @@
![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
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_cors import CORS
import json
#import hashlib
import hashlib
app = Flask(__name__)
CORS(app, resources={r"/music/*": {"origins": "http://*"}})
@@ -16,12 +16,12 @@ def set_content():
data = request.get_json()
user = data.get('user')
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.pop('user', None)
cache.pop('password', None)
return jsonify({'message': 'Content set successfully.'})
return jsonify({'message': f'Content set successfully.'})
else:
return jsonify({'message': 'Invalid user or password.'}), 401

View File

@@ -4,7 +4,8 @@ function secondsToMinutesAndSeconds(seconds) {
return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
}
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(data => {
const artist = data.artist;
@@ -15,9 +16,10 @@ function fetchDataAndAnimate() {
const itunes_url = data.itunes_url;
const name = data.name;
const timestamp = parseFloat(data.timestamp);
const decalage = (Date.now()/1000 - timestamp);
let pPosition = Math.round(parseFloat(data.pPosition) + decalage-3);
const decalage = (Date.now() / 1000 - timestamp);
let pPosition = Math.round(parseFloat(data.pPosition) + decalage - 3);
const duration = parseFloat(data.duration);
const status = data.status;
const titleSongElement = document.querySelector('.title-song');
// titleSongElement.textContent = name;
@@ -40,38 +42,55 @@ function fetchDataAndAnimate() {
element.href = itunes_url;
});
const totaltimeElement = document.querySelector('.total-time');
totaltimeElement.textContent = time;
if (status === 'playing') {
const totaltimeElement = document.querySelector('.total-time');
totaltimeElement.textContent = time;
const lasttimeElement = document.querySelector('.last-time');
lasttimeElement.textContent = secondsToMinutesAndSeconds(pPosition);
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', `${rapport.toFixed(2)}%`);
});
let intervalId;
function updateProgress() {
const lasttimeElement = document.querySelector('.last-time');
lasttimeElement.textContent = secondsToMinutesAndSeconds(pPosition);
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', `${rapport.toFixed(2)}%`);
});
pPosition++;
if (pPosition > duration) {
clearInterval(intervalId);
fetchDataAndAnimate();
}
}
intervalId = setInterval(updateProgress, 1000);
let intervalId;
function updateProgress() {
const lasttimeElement = document.querySelector('.last-time');
lasttimeElement.textContent = secondsToMinutesAndSeconds(pPosition);
const rapport = (pPosition / duration) * 100;
const trackElements = document.querySelectorAll('.track');
trackElements.forEach(element => {
element.style.setProperty('--new-width', `${rapport.toFixed(2)}%`);
});
pPosition++;
if (pPosition > duration) {
clearInterval(intervalId);
fetchDataAndAnimate();
}
}
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 => {
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")
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):
query = f"{song} {artist} {album}"
@@ -20,7 +24,6 @@ def get_track_extras(song, artist, album):
json_data = r.json()
if json_data["resultCount"] == 1:
result = json_data["results"][0]
pprint(result)
elif json_data["resultCount"] > 1:
result = json_data["results"][0]
else :
@@ -29,7 +32,6 @@ def get_track_extras(song, artist, album):
artwork_url = result["artworkUrl100"] if result else None
itunes_url = result["trackViewUrl"] 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)
@@ -43,8 +45,10 @@ def post(currentsong):
else :
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://0.0.0.0:3005"
headers = {'Content-Type': 'application/json'}
def main():
@@ -52,7 +56,8 @@ def main():
prevstatus = ''
while True:
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['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