Coder Social home page Coder Social logo

music's Introduction

Music (API)

A Spring Boot Java web application, which provides useful APIs for music management and consumption.

This is intended to be the single source of truth when using the associated music clients.

I built this after not being able to find another music program that satisfied all of my requirements: ability to allow syncing to other devices (including other computers), single source metadata management (including on the fly play count updates), and no requirement to run on Windows (ahem MediaMonkey).

Clients:

Features:

  • ID3 metadata management
  • individual device profiles
  • play count tracking (per profile)
  • skip count tracking (per profile)
  • import existing play count data from MediaMonkey database
  • conversion of music to new format (as specified in the device profile), via FFmpeg
  • (basic) websocket implementation for live updates of long running tasks
  • "smart" playlists which update on the fly as track metadata changes
  • metadata changes are cached, and written to disk at a later time (when requested) - to avoid unnecessary hard drive spin ups as metadata is modified
  • local folder monitoring, where newly added files are automatically uploaded

Reverse proxy via nginx

location /Music {
	# CORS requires unauthenticated OPTIONS calls to succeed
	limit_except OPTIONS {
		auth_basic "Restricted content";
		auth_basic_user_file .htpasswd;
	}
	include /config/nginx/proxy.conf;
	proxy_pass http://host:port;
	# WebSocket connection requires this
	proxy_set_header Upgrade $http_upgrade;
	proxy_set_header Connection "upgrade";
}

music's People

Contributors

dependabot[bot] avatar stevenmassaro avatar

Watchers

 avatar

music's Issues

queued track updates logic is too complex

It's not really necessary to have a whole separate table for storing queued track updates. Instead, can just modify the fields directly in the track table and add a new column called "ID3changed" or "ID3dirty", then compare the actual ID3 tags with what's in the track table and apply the necessary changes.

replacing a track fails midway through

music.service.TrackService#uploadNewTrack(java.io.InputStream, java.lang.String, long)

needs to have something like:

		} else if (!syncResult.getModifiedTracks().isEmpty() && !replacingExistingTrack) {
			// todo if replacing an existing track, this exception is expected

unable to apply some track updates to disk

2023-04-06T22:46:15.467Z ERROR 1 --- [nio-8080-exec-7] music.service.UpdateService              : Failed to apply update to disk: TrackUpdate(id=324, songId=9478, field='year', newValue='1971', updateType=1)

java.util.concurrent.ExecutionException: org.jaudiotagger.audio.exceptions.CannotReadException: /music/MUSIC/Music/Mike Curb Congregation/Greatest Hits/1 - Burning Bridges From the Motion Picture Kellys Heroes.flac:null
        at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:502) ~[guava-23.0.jar!/:na]
        at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:461) ~[guava-23.0.jar!/:na]
        at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:83) ~[guava-23.0.jar!/:na]
        at com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly(Uninterruptibles.java:142) ~[guava-23.0.jar!/:na]
        at com.google.common.cache.LocalCache$Segment.getAndRecordStats(LocalCache.java:2453) ~[guava-23.0.jar!/:na]
        at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2417) ~[guava-23.0.jar!/:na]
        at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2299) ~[guava-23.0.jar!/:na]
        at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2212) ~[guava-23.0.jar!/:na]
        at com.google.common.cache.LocalCache.get(LocalCache.java:4147) ~[guava-23.0.jar!/:na]
        at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:5053) ~[guava-23.0.jar!/:na]
        at music.service.MetadataService.updateTrackField(MetadataService.kt:126) ~[music-service-1.18.4.jar!/:1.18.4]
        at music.service.UpdateService.applyUpdatesToDisk(UpdateService.kt:86) ~[classes!/:1.18.4]
        at music.endpoint.AdminEndpoint.applyUpdatesToSongs(AdminEndpoint.java:113) ~[classes!/:1.18.4]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[spring-web-6.0.7.jar!/:6.0.7]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[spring-web-6.0.7.jar!/:6.0.7]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:563) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.7.jar!/:6.0.7]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:631) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-10.1.7.jar!/:na]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.7.jar!/:6.0.7]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.7.jar!/:6.0.7]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.7.jar!/:6.0.7]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.7.jar!/:6.0.7]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.7.jar!/:6.0.7]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.7.jar!/:6.0.7]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.7.jar!/:na]
        at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Caused by: org.jaudiotagger.audio.exceptions.CannotReadException: /music/MUSIC/Music/Mike Curb Congregation/Greatest Hits/1 - Burning Bridges From the Motion Picture Kellys Heroes.flac:null
        at org.jaudiotagger.audio.generic.AudioFileReader.read(AudioFileReader.java:117) ~[jaudiotagger-2.0.3.jar!/:na]
        at org.jaudiotagger.audio.AudioFileIO.readFile(AudioFileIO.java:286) ~[jaudiotagger-2.0.3.jar!/:na]
        at org.jaudiotagger.audio.AudioFileIO.read(AudioFileIO.java:150) ~[jaudiotagger-2.0.3.jar!/:na]
        at music.service.MetadataService.updateTrackField$lambda$2(MetadataService.kt:126) ~[music-service-1.18.4.jar!/:1.18.4]
        at com.google.common.cache.LocalCache$LocalManualCache$1.load(LocalCache.java:5058) ~[guava-23.0.jar!/:na]
        at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3708) ~[guava-23.0.jar!/:na]
        at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2416) ~[guava-23.0.jar!/:na]
        ... 57 common frames omitted
Caused by: java.nio.BufferUnderflowException: null
        at java.base/java.nio.HeapByteBuffer.get(HeapByteBuffer.java:183) ~[na:na]
        at java.base/java.nio.ByteBuffer.get(ByteBuffer.java:826) ~[na:na]
        at org.jaudiotagger.audio.flac.metadatablock.MetadataBlockDataPicture.initFromByteBuffer(MetadataBlockDataPicture.java:91) ~[jaudiotagger-2.0.3.jar!/:na]
        at org.jaudiotagger.audio.flac.metadatablock.MetadataBlockDataPicture.<init>(MetadataBlockDataPicture.java:126) ~[jaudiotagger-2.0.3.jar!/:na]
        at org.jaudiotagger.audio.flac.FlacTagReader.read(FlacTagReader.java:86) ~[jaudiotagger-2.0.3.jar!/:na]
        at org.jaudiotagger.audio.flac.FlacFileReader.getTag(FlacFileReader.java:45) ~[jaudiotagger-2.0.3.jar!/:na]
        at org.jaudiotagger.audio.generic.AudioFileReader.read(AudioFileReader.java:106) ~[jaudiotagger-2.0.3.jar!/:na]
        ... 63 common frames omitted

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.