diff --git a/.idea/compiler.xml b/.idea/compiler.xml index f8c449a..4b5e866 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -14,7 +14,6 @@ - diff --git a/pom.xml b/pom.xml index fe2a4a3..76156b0 100644 --- a/pom.xml +++ b/pom.xml @@ -118,7 +118,7 @@ com.github.TeamNewPipe NewPipeExtractor - v0.24.6 + v0.26.1 com.squareup.okhttp3 diff --git a/src/main/java/cc/wordview/api/controller/LyricsController.java b/src/main/java/cc/wordview/api/controller/TextTracksController.java similarity index 66% rename from src/main/java/cc/wordview/api/controller/LyricsController.java rename to src/main/java/cc/wordview/api/controller/TextTracksController.java index 9d00ec9..1042c0a 100644 --- a/src/main/java/cc/wordview/api/controller/LyricsController.java +++ b/src/main/java/cc/wordview/api/controller/TextTracksController.java @@ -18,11 +18,10 @@ package cc.wordview.api.controller; import cc.wordview.api.Application; -import cc.wordview.api.response.LyricsResponse; -import cc.wordview.api.service.implementation.LyricsService; -import cc.wordview.api.service.VideoLyricsServiceInterface; -import cc.wordview.api.util.ArrayUtil; +import cc.wordview.api.response.TextTrackResponse; import cc.wordview.api.runtime.ResourceResolver; +import cc.wordview.api.service.implementation.TextTracksService; +import cc.wordview.api.util.ArrayUtil; import cc.wordview.gengolex.Language; import cc.wordview.gengolex.LanguageNotFoundException; import cc.wordview.gengolex.Parser; @@ -35,38 +34,48 @@ import java.io.IOException; import java.net.URLDecoder; import java.util.ArrayList; +import java.util.List; import static cc.wordview.api.controller.response.Response.ok; @RestController @CrossOrigin(origins = Application.CORS_ORIGIN) -@RequestMapping(path = Application.API_PATH + "/lyrics") -public class LyricsController extends ServiceController { +@RequestMapping(path = Application.API_PATH + "/text-tracks") +public class TextTracksController extends ServiceController { @Autowired private ResourceResolver resourceResolver; - @Autowired - private VideoLyricsServiceInterface videoLyricsService; - - @GetMapping(produces = "application/json;charset=utf-8") + @GetMapping(produces = "application/json;charset=utf-8", path = "/lyrics") public ResponseEntity getLyrics(@RequestParam String id, @RequestParam String lang, @RequestParam String trackName, @RequestParam String artistName) throws IOException, LyricsNotFoundException, LanguageNotFoundException { String decodedTrackName = URLDecoder.decode(trackName); String decodedArtistName = URLDecoder.decode(artistName); String lyrics = service.getLyrics(id, decodedTrackName, decodedArtistName, lang); - String dictionariesPath = resourceResolver.getDictionariesPath(); + ArrayList words = getContainingWords(lyrics, lang); - Parser parser = new Parser(Language.Companion.byTag(lang), dictionariesPath); - - ArrayList words = ArrayUtil.withoutDuplicates(parser.findWords(lyrics.replace("\n", " "))); - - return ok(new LyricsResponse(lyrics, words)); + return ok(new TextTrackResponse(lyrics, words)); } @GetMapping(produces = "application/json;charset=utf-8", path = "/list") public ResponseEntity getLyricsList() { - ArrayList ids = videoLyricsService.listLyricsIds(); + List ids = service.listAvailableTracks(); return ok(ids); } + + @GetMapping(produces = "application/json;charset=utf-8", path = "/subtitles") + public ResponseEntity getSubtitle(@RequestParam String id, @RequestParam String lang) throws IOException, LanguageNotFoundException { + String subtitle = service.getSubtitle(id, lang); + ArrayList words = getContainingWords(subtitle, lang); + + return ok(new TextTrackResponse(subtitle, words)); + } + + private ArrayList getContainingWords(String vttFile, String lang) throws IOException, LanguageNotFoundException { + String dictionariesPath = resourceResolver.getDictionariesPath(); + + Parser parser = new Parser(Language.Companion.byTag(lang), dictionariesPath); + + return ArrayUtil.withoutDuplicates(parser.findWords(vttFile.replace("\n", " "))); + } } diff --git a/src/main/java/cc/wordview/api/controller/response/ApiExceptionHandler.java b/src/main/java/cc/wordview/api/controller/response/ApiExceptionHandler.java index 7078d44..7a8e228 100644 --- a/src/main/java/cc/wordview/api/controller/response/ApiExceptionHandler.java +++ b/src/main/java/cc/wordview/api/controller/response/ApiExceptionHandler.java @@ -55,7 +55,8 @@ public ResponseEntity badRequest(Exception e) { FileNotFoundException.class, LyricsNotFoundException.class, LanguageNotFoundException.class, - ImageNotFoundException.class + ImageNotFoundException.class, + SubtitleNotFoundException.class }) public ResponseEntity notFound(Exception e) { return error(HttpStatus.NOT_FOUND, e); diff --git a/src/main/java/cc/wordview/api/database/entity/VideoLyrics.java b/src/main/java/cc/wordview/api/database/entity/TextTrack.java similarity index 84% rename from src/main/java/cc/wordview/api/database/entity/VideoLyrics.java rename to src/main/java/cc/wordview/api/database/entity/TextTrack.java index fda6be8..e426f09 100644 --- a/src/main/java/cc/wordview/api/database/entity/VideoLyrics.java +++ b/src/main/java/cc/wordview/api/database/entity/TextTrack.java @@ -25,8 +25,8 @@ @Entity @Data -@Table(name = "video_lyrics") -public class VideoLyrics implements Serializable { +@Table(name = "text_tracks") +public class TextTrack implements Serializable { @Serial private static final long serialVersionUID = 1854932248236429655L; @@ -38,9 +38,6 @@ public class VideoLyrics implements Serializable { @Column(name = "video_id", unique = true) private String videoId; - @Column(name = "lyrics_file") - private String lyricsFile; - - @Column(name = "time_offset") - private int offset; + @Column(name = "file") + private String file; } diff --git a/src/main/java/cc/wordview/api/service/VideoLyricsServiceInterface.java b/src/main/java/cc/wordview/api/exception/SubtitleNotFoundException.java similarity index 62% rename from src/main/java/cc/wordview/api/service/VideoLyricsServiceInterface.java rename to src/main/java/cc/wordview/api/exception/SubtitleNotFoundException.java index 3a5cdf0..8afb110 100644 --- a/src/main/java/cc/wordview/api/service/VideoLyricsServiceInterface.java +++ b/src/main/java/cc/wordview/api/exception/SubtitleNotFoundException.java @@ -15,18 +15,10 @@ * along with this program. If not, see . */ -package cc.wordview.api.service; +package cc.wordview.api.exception; -import cc.wordview.api.database.entity.VideoLyrics; -import cc.wordview.api.exception.NoSuchEntryException; - -import java.util.ArrayList; -import java.util.List; - -public interface VideoLyricsServiceInterface extends ServiceInterface { - VideoLyrics getByVideoId(String videoId) throws NoSuchEntryException; - - ArrayList listLyricsIds(); - - List getAll(); +public class SubtitleNotFoundException extends RuntimeException { + public SubtitleNotFoundException(String message) { + super(message); + } } diff --git a/src/main/java/cc/wordview/api/repository/VideoLyricsRepository.java b/src/main/java/cc/wordview/api/repository/TextTracksRepository.java similarity index 81% rename from src/main/java/cc/wordview/api/repository/VideoLyricsRepository.java rename to src/main/java/cc/wordview/api/repository/TextTracksRepository.java index 9ae7054..f73070f 100644 --- a/src/main/java/cc/wordview/api/repository/VideoLyricsRepository.java +++ b/src/main/java/cc/wordview/api/repository/TextTracksRepository.java @@ -17,11 +17,11 @@ package cc.wordview.api.repository; -import cc.wordview.api.database.entity.VideoLyrics; +import cc.wordview.api.database.entity.TextTrack; import org.springframework.data.repository.CrudRepository; import java.util.Optional; -public interface VideoLyricsRepository extends CrudRepository { - Optional findByVideoId(String videoId); +public interface TextTracksRepository extends CrudRepository { + Optional findByVideoId(String videoId); } diff --git a/src/main/java/cc/wordview/api/response/LyricsResponse.java b/src/main/java/cc/wordview/api/response/TextTrackResponse.java similarity index 93% rename from src/main/java/cc/wordview/api/response/LyricsResponse.java rename to src/main/java/cc/wordview/api/response/TextTrackResponse.java index ba292f9..cf9c849 100644 --- a/src/main/java/cc/wordview/api/response/LyricsResponse.java +++ b/src/main/java/cc/wordview/api/response/TextTrackResponse.java @@ -25,7 +25,7 @@ @Data @AllArgsConstructor -public class LyricsResponse { - private String lyrics; +public class TextTrackResponse { + private String textTrack; private List dictionary; } diff --git a/src/main/java/cc/wordview/api/runtime/ResourceResolver.java b/src/main/java/cc/wordview/api/runtime/ResourceResolver.java index bcbfe56..598f6ea 100644 --- a/src/main/java/cc/wordview/api/runtime/ResourceResolver.java +++ b/src/main/java/cc/wordview/api/runtime/ResourceResolver.java @@ -43,6 +43,12 @@ public class ResourceResolver { @Value("${wordview.lyrics_path}") private String lyricsPath; + @Value("${wordview.subtitles_path}") + private String subtitlesPath; + + @Value("${wordview.text_tracks_path}") + private String textTracksPath; + @Value("${wordview.phrases_path}") private String phrasesPath; @@ -59,11 +65,13 @@ public void debugShowPaths() { Dictionaries Path: {} Images Path: {} Lyrics Path: {} + Subtitles Path: {} + Text Tracks Path: {} Phrases Path: {} Translations Path: {} Feeds Path: {} """, - dictionariesPath, imagesPath, lyricsPath, phrasesPath, translationsPath, feedsPath); + dictionariesPath, imagesPath, lyricsPath, subtitlesPath, textTracksPath, phrasesPath, translationsPath, feedsPath); } public String getDictionariesPath() throws IOException { @@ -78,6 +86,14 @@ public String getLyricsPath() throws IOException { return resolvePathOrClasspath(lyricsPath); } + public String getSubtitlesPath() throws IOException { + return resolvePathOrClasspath(subtitlesPath); + } + + public String getTextTracksPath() throws IOException { + return resolvePathOrClasspath(textTracksPath); + } + public String getPhrasesPath() throws IOException { return resolvePathOrClasspath(phrasesPath); } diff --git a/src/main/java/cc/wordview/api/runtime/cache/LyricsCache.java b/src/main/java/cc/wordview/api/runtime/cache/TextTrackCache.java similarity index 54% rename from src/main/java/cc/wordview/api/runtime/cache/LyricsCache.java rename to src/main/java/cc/wordview/api/runtime/cache/TextTrackCache.java index 17f7e49..018ff8c 100644 --- a/src/main/java/cc/wordview/api/runtime/cache/LyricsCache.java +++ b/src/main/java/cc/wordview/api/runtime/cache/TextTrackCache.java @@ -17,9 +17,9 @@ package cc.wordview.api.runtime.cache; -import cc.wordview.api.database.entity.VideoLyrics; +import cc.wordview.api.database.entity.TextTrack; +import cc.wordview.api.repository.TextTracksRepository; import cc.wordview.api.runtime.ResourceResolver; -import cc.wordview.api.service.VideoLyricsServiceInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -34,50 +34,48 @@ import java.util.stream.Stream; @Component -public class LyricsCache extends HashMapCacheManager { - private static final Logger logger = LoggerFactory.getLogger(LyricsCache.class); +public class TextTrackCache extends HashMapCacheManager { + private static final Logger logger = LoggerFactory.getLogger(TextTrackCache.class); @Autowired private ResourceResolver resourceResolver; @Autowired - private VideoLyricsServiceInterface videoLyricsService; + private TextTracksRepository repository; @Override public void init() throws IOException { - String lyricsPath = resourceResolver.getLyricsPath(); - Path filepath = Path.of(lyricsPath); + String subtitlesPath = resourceResolver.getTextTracksPath(); + Path filepath = Path.of(subtitlesPath); Map fileToContent = new HashMap<>(); try (Stream paths = Files.walk(filepath)) { - paths.filter(Files::isRegularFile) - .forEach(file -> { - try { - fileToContent.put(file.getFileName().toString(), Files.readString(file)); - } catch (IOException e) { - logger.error("Failed to read lyrics file, ignoring this file", e); - } - }); + paths.filter(Files::isRegularFile).forEach(file -> { + try { + fileToContent.put(file.getFileName().toString(), Files.readString(file)); + } catch (IOException e) { + logger.error("Failed to read subtitle file, ignoring this file", e); + } + }); - List videoLyrics = videoLyricsService.getAll(); + List textTracks = (List) repository.findAll(); - for (var videoLyric : videoLyrics) { - String content = fileToContent.get(videoLyric.getLyricsFile() + ".vtt"); - map.put(videoLyric.getVideoId(), content); + for (var subtitle : textTracks) { + String content = fileToContent.get(subtitle.getFile()); + map.put(subtitle.getVideoId(), content); } - logger.info("Preloaded {} lyrics", map.size()); + logger.info("Preloaded {} text tracks", map.size()); } catch (IOException e) { - logger.error("Failed to read the lyrics path", e); + logger.error("Failed to read the text tracks path", e); } } - public void put(String id, String lyrics) { + public void put(String id, String subtitle) { if (map.containsKey(id)) return; - - logger.info("Adding {} to cache, there are now {} lyrics", id, map.size() + 1); - map.put(id, lyrics); + logger.info("Adding {} to cache, there are now {} text tracks", id, map.size() + 1); + map.put(id, subtitle); } } diff --git a/src/main/java/cc/wordview/api/service/LyricsServiceInterface.java b/src/main/java/cc/wordview/api/service/TextTracksServiceInterface.java similarity index 74% rename from src/main/java/cc/wordview/api/service/LyricsServiceInterface.java rename to src/main/java/cc/wordview/api/service/TextTracksServiceInterface.java index be5790a..74167d7 100644 --- a/src/main/java/cc/wordview/api/service/LyricsServiceInterface.java +++ b/src/main/java/cc/wordview/api/service/TextTracksServiceInterface.java @@ -17,12 +17,14 @@ package cc.wordview.api.service; +import cc.wordview.api.exception.SubtitleNotFoundException; import cc.wordview.wordfind.exception.LyricsNotFoundException; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import java.io.IOException; +import java.util.List; -public interface LyricsServiceInterface { +public interface TextTracksServiceInterface { /** * Retrieves the lyrics for a track * @@ -37,6 +39,17 @@ public interface LyricsServiceInterface { */ String getLyrics(String id, String trackName, String artistName, String langTag) throws ExtractionException, IOException, LyricsNotFoundException; + /** + * Retrieves the subtitles for the track given the `id` + * @param id the YouTube id of the video. + * @param langTag the language the subtitle should be in. + * @return the subtitles formatted in VTT. + * @throws ExtractionException if subtitle extraction from YouTube fails. + * @throws IOException if reading subtitles from disk fails. + * @throws SubtitleNotFoundException if no valid subtitles are found. + */ + String getSubtitle(String id, String langTag) throws ExtractionException, IOException, SubtitleNotFoundException; + /** * Attempts to retrieve lyrics for the given track and artist from an external provider from WordFind. * @@ -47,5 +60,10 @@ public interface LyricsServiceInterface { * @throws LyricsNotFoundException if no lyrics are found for the provided track and artist. */ String getLyricsExternal(String trackName, String artistName) throws IOException, LyricsNotFoundException; + + /** + * Retrieves a list of the tracks currently provided by the API. + */ + List listAvailableTracks(); } diff --git a/src/main/java/cc/wordview/api/service/implementation/LyricsService.java b/src/main/java/cc/wordview/api/service/implementation/TextTracksService.java similarity index 53% rename from src/main/java/cc/wordview/api/service/implementation/LyricsService.java rename to src/main/java/cc/wordview/api/service/implementation/TextTracksService.java index e31f790..0682c44 100644 --- a/src/main/java/cc/wordview/api/service/implementation/LyricsService.java +++ b/src/main/java/cc/wordview/api/service/implementation/TextTracksService.java @@ -17,9 +17,11 @@ package cc.wordview.api.service.implementation; -import cc.wordview.api.runtime.cache.LyricsCache; -import cc.wordview.api.service.LyricsServiceInterface; -import cc.wordview.api.runtime.DownloaderImpl; +import cc.wordview.api.database.entity.TextTrack; +import cc.wordview.api.exception.SubtitleNotFoundException; +import cc.wordview.api.repository.TextTracksRepository; +import cc.wordview.api.runtime.cache.TextTrackCache; +import cc.wordview.api.service.TextTracksServiceInterface; import cc.wordview.wordfind.WordFind; import cc.wordview.wordfind.exception.LyricsNotFoundException; import jakarta.annotation.PostConstruct; @@ -28,32 +30,41 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.SubtitlesStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; @Service -public class LyricsService implements LyricsServiceInterface { +public class TextTracksService implements TextTracksServiceInterface { + private static final Logger logger = LoggerFactory.getLogger(TextTracksService.class); private static StreamingService YTService; private final WordFind client = new WordFind(); @Autowired - private LyricsCache cache; + private TextTrackCache cache; + + @Autowired + private TextTracksRepository repository; @Override - public String getLyrics(String id, String trackName, String artistName, String langTag) throws IOException, LyricsNotFoundException { + public String getLyrics(String id, String trackName, String artistName, String langTag) throws LyricsNotFoundException { String lyrics = cache.get(id); - if (lyrics == null) - lyrics = getLyricsYT(id, langTag); + if (lyrics == null) { + lyrics = getYouTubeSubtitle(id, langTag); + } - if (lyrics == null) + if (lyrics == null) { lyrics = getLyricsExternal(trackName, artistName); - + } if (Objects.equals(lyrics, "WEBVTT\n\n# This WEBVTT was converted from LRC and might contain errors\n\n")) { throw new LyricsNotFoundException("Unable to find lyrics for %s".formatted(trackName)); @@ -63,24 +74,39 @@ public String getLyrics(String id, String trackName, String artistName, String l return lyrics; } - private String getLyricsYT(String id, String langTag) { - StreamInfo info; + @Override + public String getSubtitle(String id, String langTag) throws SubtitleNotFoundException { + String subtitle = cache.get(id); + + if (subtitle == null) { + subtitle = getYouTubeSubtitle(id, langTag); + } - try { - info = StreamInfo.getInfo(YTService, "https://youtube.com/watch?v=" + id); - } catch (Exception e) { - return null; + if (subtitle == null) { + throw new SubtitleNotFoundException("Unable to find any subtitles for the video %s".formatted(id)); } - for (SubtitlesStream subtitle : info.getSubtitles()) { - if (subtitle.isAutoGenerated()) continue; + cache.put(id, subtitle); + + return subtitle; + } - if (Objects.equals(subtitle.getLanguageTag(), langTag)) { - String url = subtitle.getContent().replace("&fmt=ttml", "&fmt=vtt"); - RestTemplate restTemplate = new RestTemplate(); - return restTemplate.getForObject(url, String.class); + private String getYouTubeSubtitle(String id, String lang) { + try { + StreamInfo info = StreamInfo.getInfo(YTService, "https://youtube.com/watch?v=" + id); + + for (SubtitlesStream subtitle : info.getSubtitles()) { + if (subtitle.isAutoGenerated()) continue; + + if (Objects.equals(subtitle.getLanguageTag(), lang)) { + String url = subtitle.getContent().replace("&fmt=ttml", "&fmt=vtt"); + + return new RestTemplate().getForObject(url, String.class); + } } + } catch (Exception e) { + logger.error("Failed to retrieve subtitles from YouTube", e); } return null; @@ -100,4 +126,16 @@ private void preloadLyrics() throws IOException { public String getLyricsExternal(String trackName, String artistName) throws LyricsNotFoundException { return client.find(trackName, artistName, true, null, null); } + + @Override + public List listAvailableTracks() { + Iterable tracks = repository.findAll(); + ArrayList ids = new ArrayList<>(); + + for (TextTrack track : tracks) { + ids.add(track.getVideoId()); + } + + return ids; + } } diff --git a/src/main/java/cc/wordview/api/service/implementation/VideoLyricsService.java b/src/main/java/cc/wordview/api/service/implementation/VideoLyricsService.java deleted file mode 100644 index 6af2416..0000000 --- a/src/main/java/cc/wordview/api/service/implementation/VideoLyricsService.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2025 Arthur Araujo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package cc.wordview.api.service.implementation; - -import cc.wordview.api.database.entity.VideoLyrics; -import cc.wordview.api.exception.NoSuchEntryException; -import cc.wordview.api.repository.VideoLyricsRepository; -import cc.wordview.api.service.VideoLyricsServiceInterface; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -@Service -public class VideoLyricsService implements VideoLyricsServiceInterface { - @Autowired - private VideoLyricsRepository repository; - - @Override - public VideoLyrics getByVideoId(String videoId) throws NoSuchEntryException { - Optional videoLyrics = repository.findByVideoId(videoId); - - if (!videoLyrics.isPresent()) { - throw new NoSuchEntryException("Unable to find any lyrics with this videoId"); - } - - return videoLyrics.get(); - } - - @Override - public ArrayList listLyricsIds() { - Iterable allLyrics = repository.findAll(); - ArrayList ids = new ArrayList<>(); - - for (VideoLyrics lyric : allLyrics) { - ids.add(lyric.getVideoId()); - } - - return ids; - } - - @Override - public List getAll() { - return (List) repository.findAll(); - } - - - @Override - public VideoLyrics getById(Long id) { - throw new UnsupportedOperationException("getById is disabled for VideoLyricsService"); - } - - @Override - public VideoLyrics insert(VideoLyrics entity) { - // They should be manually added to the sql - throw new UnsupportedOperationException("insert is disabled for VideoLyricsService"); - } -} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index b7415a5..39eb5f1 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -18,6 +18,8 @@ spring.jpa.open-in-view=false wordview.dictionaries_path=classpath:/dictionaries/ wordview.images_path=classpath:/images/ wordview.lyrics_path=classpath:/lyrics/ +wordview.subtitles_path=classpath:/subtitles/ +wordview.text_tracks_path=classpath:/text-tracks/ wordview.phrases_path=classpath:/phrases/ wordview.translations_path=classpath:/translations/ wordview.feeds_path=classpath:/feeds/ diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index ee92e76..a0ae9c6 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -16,6 +16,6 @@ spring.jpa.open-in-view=false wordview.dictionaries_path= wordview.images_path= -wordview.lyrics_path= +wordview.text_tracks_path= wordview.phrases_path= wordview.feeds_path= \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 8bd4a0b..7bd9a62 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,23 +1,29 @@ -INSERT INTO member (username, email, password) VALUES - ('Arthur', 'arthur.araujo@tutanota.com', 'senha'); +INSERT INTO member (username, email, password) +VALUES ('Arthur', 'arthur.araujo@tutanota.com', 'senha'); -INSERT INTO member (username, email, password) VALUES - ('conta', 'conta2@tutanota.com', 'senha'); +INSERT INTO member (username, email, password) +VALUES ('conta', 'conta2@tutanota.com', 'senha'); -INSERT INTO member (username, email, password, role) VALUES - ('mock.user', 'mock.user@gmail.com', '8631b7298388ab36f3785770cc07c4ea', 'USER'); +INSERT INTO member (username, email, password, role) +VALUES ('mock.user', 'mock.user@gmail.com', '8631b7298388ab36f3785770cc07c4ea', 'USER'); -INSERT INTO member (username, email, password, role) VALUES - ('mock.disposable', 'mock.disposable@gmail.com', '5b4a4769301f10a5ac64609e7e0bba0e', 'USER'); +INSERT INTO member (username, email, password, role) +VALUES ('mock.disposable', 'mock.disposable@gmail.com', '5b4a4769301f10a5ac64609e7e0bba0e', 'USER'); -INSERT INTO member (username, email, password, role) VALUES - ('mock.disposable.email', 'mock.disposable.email@gmail.com', '407c8797b415f2e557dc141d16c4ac86', 'USER'); +INSERT INTO member (username, email, password, role) +VALUES ('mock.disposable.email', 'mock.disposable.email@gmail.com', '407c8797b415f2e557dc141d16c4ac86', 'USER'); -INSERT INTO member (username, email, password, role) VALUES - ('mock.admin', 'mock.admin@gmail.com', '061cf224cffe1951e32ffaa1c414544a', 'ADMIN'); +INSERT INTO member (username, email, password, role) +VALUES ('mock.admin', 'mock.admin@gmail.com', '061cf224cffe1951e32ffaa1c414544a', 'ADMIN'); -INSERT INTO email (email) VALUES ('mock.user@gmail.com'); +INSERT INTO email (email) +VALUES ('mock.user@gmail.com'); -INSERT INTO known_words (user_id, lang, words) VALUES (3, 'en', 'rain,world'); +INSERT INTO known_words (user_id, lang, words) +VALUES (3, 'en', 'rain,world'); -INSERT INTO video_lyrics (video_id, lyrics_file, time_offset) VALUES ('ZnUEeXpxBJ0', 'aquarela', 0); \ No newline at end of file +INSERT INTO text_tracks (video_id, file) +VALUES ('ZnUEeXpxBJ0', 'lyrics/aquarela.vtt'); + +INSERT INTO text_tracks (video_id, file) +VALUES ('K9Ydi94yT94', 'subtitles/uma_wo_kakudaisuru_ge-mu.vtt'); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 2660a16..d3fcc88 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,35 +1,38 @@ DROP TABLE IF EXISTS member CASCADE; DROP TABLE IF EXISTS email CASCADE; -DROP TABLE IF EXISTS video_lyrics CASCADE; +DROP TABLE IF EXISTS text_tracks CASCADE; -CREATE TABLE member ( - id bigint GENERATED ALWAYS AS IDENTITY, - username varchar(255), - email varchar(255) UNIQUE, - password varchar(255), - role varchar(255), +CREATE TABLE member +( + id bigint GENERATED ALWAYS AS IDENTITY, + username varchar(255), + email varchar(255) UNIQUE, + password varchar(255), + role varchar(255), lesson_time bigint DEFAULT 300000, PRIMARY KEY (id) ); -CREATE TABLE email ( - id bigint GENERATED ALWAYS AS IDENTITY, +CREATE TABLE email +( + id bigint GENERATED ALWAYS AS IDENTITY, email varchar(255) UNIQUE, PRIMARY KEY (id) ); -CREATE TABLE video_lyrics ( - id bigint GENERATED ALWAYS AS IDENTITY, - video_id varchar(12) UNIQUE, - lyrics_file varchar(128), - time_offset int, +CREATE TABLE text_tracks +( + id bigint GENERATED ALWAYS AS IDENTITY, + video_id varchar(12) UNIQUE, + file varchar(128), PRIMARY KEY (id) ); -CREATE TABLE known_words ( - id bigint GENERATED ALWAYS AS IDENTITY, +CREATE TABLE known_words +( + id bigint GENERATED ALWAYS AS IDENTITY, user_id bigint, - lang varchar(64), - words varchar(5120), + lang varchar(64), + words varchar(5120), PRIMARY KEY (id) ); diff --git a/src/main/resources/lyrics/aquarela.vtt b/src/main/resources/text-tracks/lyrics/aquarela.vtt similarity index 100% rename from src/main/resources/lyrics/aquarela.vtt rename to src/main/resources/text-tracks/lyrics/aquarela.vtt diff --git a/src/main/resources/text-tracks/subtitles/uma_wo_kakudaisuru_ge-mu.vtt b/src/main/resources/text-tracks/subtitles/uma_wo_kakudaisuru_ge-mu.vtt new file mode 100644 index 0000000..60c772e --- /dev/null +++ b/src/main/resources/text-tracks/subtitles/uma_wo_kakudaisuru_ge-mu.vtt @@ -0,0 +1,8 @@ +WEBVTT + +00:00:0.100 --> 00:00:3.000 +馬を拡大して面白い形にしたいなって + +00:00:3.000 --> 00:00:4.500 +思ったことはありますか? + diff --git a/src/test/java/cc/wordview/api/test/api/controller/LyricsControllerTest.java b/src/test/java/cc/wordview/api/test/api/controller/TextTracksControllerTest.java similarity index 65% rename from src/test/java/cc/wordview/api/test/api/controller/LyricsControllerTest.java rename to src/test/java/cc/wordview/api/test/api/controller/TextTracksControllerTest.java index 0a35923..4e05cd5 100644 --- a/src/test/java/cc/wordview/api/test/api/controller/LyricsControllerTest.java +++ b/src/test/java/cc/wordview/api/test/api/controller/TextTracksControllerTest.java @@ -26,19 +26,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -class LyricsControllerTest extends ControllerTest { +class TextTracksControllerTest extends ControllerTest { @Test @Disabled("Makes a external request, only run this when ABSOLUTELY NECESSARY") void getLyrics() throws Exception { - req.get("/lyrics?id=1cGQotpn8r4&lang=ja&trackName=a&artistName=a") + req.get("/text-tracks/lyrics?id=1cGQotpn8r4&lang=ja&trackName=a&artistName=a") .andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=utf-8")); } @Test -// @Disabled("Does not make external request but don't seem to work on CI") + @Disabled("Does not make external request but don't seem to work on CI") void getLyricsWordView() throws Exception { - req.get("/lyrics?id=ZnUEeXpxBJ0&lang=pt&trackName=aquarela&artistName=toquinho") + req.get("/text-tracks/lyrics?id=ZnUEeXpxBJ0&lang=pt&trackName=aquarela&artistName=toquinho") .andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=utf-8")); } @@ -46,7 +46,7 @@ void getLyricsWordView() throws Exception { @Test @Disabled("Makes a external request, only run this when ABSOLUTELY NECESSARY") void getLyricsWordFind() throws Exception { - req.get("/lyrics?id=vcw5THyM7Jo&lang=ja&trackName=%s&artistName=%s".formatted( + req.get("/text-tracks/lyrics?id=vcw5THyM7Jo&lang=ja&trackName=%s&artistName=%s".formatted( URLEncoder.encode("終点の先が在るとするならば。"), URLEncoder.encode("ツユ") )) @@ -58,7 +58,7 @@ void getLyricsWordFind() throws Exception { @Test @Disabled("Makes a external request, only run this when ABSOLUTELY NECESSARY") void getLyricsNotFound() throws Exception { - req.get("/lyrics?id=vcw5THyM7Jo&lang=ja&trackName=%s&artistName=%s".formatted( + req.get("/text-tracks/lyrics?id=vcw5THyM7Jo&lang=ja&trackName=%s&artistName=%s".formatted( URLEncoder.encode("a_song_that_doesnt_exist"), URLEncoder.encode("aa") )).andExpect(status().isNotFound()); @@ -66,7 +66,23 @@ void getLyricsNotFound() throws Exception { @Test void getListOfLyricsIds() throws Exception { - req.get("/lyrics/list") + req.get("/text-tracks/list") + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json;charset=utf-8")); + } + + @Test + @Disabled("Makes a external request, only run this when ABSOLUTELY NECESSARY") + void getSubtitlesYouTube() throws Exception { + req.get("/text-tracks/subtitles?id=eCipMjo1vSI&lang=ja") + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json;charset=utf-8")); + } + + @Test + @Disabled("Disabled until https://github.com/word-view/APIWordView/issues/50 is fixed") + void getSubtitlesWordView() throws Exception { + req.get("/text-tracks/subtitles?id=K9Ydi94yT94&lang=ja") .andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=utf-8")); }