Merge branch 'unstable' into Add-Episode-Aired-Date-to-details

This commit is contained in:
candry7731 2022-12-06 14:44:02 -06:00 committed by GitHub
commit f2498d93da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 4385 additions and 246 deletions

View File

@ -9,7 +9,7 @@ jobs:
permissions:
pull-requests: write
steps:
- uses: actions/stale@3de2653986ebd134983c79fe2be5d45cc3d9f4e1 # tag=v6
- uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # tag=v6
with:
days-before-issue-stale: -1
days-before-issue-close: -1

View File

@ -8,14 +8,14 @@ jobs:
run:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
- uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3
- uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
with:
node-version: "14.12.0"
- run: npm ci
- run: npx ropm install
- run: make dev
- uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
with:
name: Jellyfin-Roku-dev-${{ github.sha }}
path: ${{ github.workspace }}/out/staging

View File

@ -22,7 +22,7 @@ jobs:
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "MINOR="$2; }' manifest >> $GITHUB_ENV
- name: "Find and save build_version from manifest"
run: awk 'BEGIN { FS="=" } /^build_version/ { print "BUILD="$2; }' manifest >> $GITHUB_ENV
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3
- uses: vimtor/action-zip@5f1c4aa587ea41db1110df6a99981dbe19cee310 # tag=v1
with:
recursive: false
@ -36,7 +36,7 @@ jobs:
prerelease: false
title: v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}
files: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
- uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
with:
name: jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
path: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip

View File

@ -22,7 +22,7 @@ jobs:
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "MINOR="$2; }' manifest >> $GITHUB_ENV
- name: "Find and save build_version from manifest"
run: awk 'BEGIN { FS="=" } /^build_version/ { print "BUILD="$2; }' manifest >> $GITHUB_ENV
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3
- uses: vimtor/action-zip@5f1c4aa587ea41db1110df6a99981dbe19cee310 # tag=v1
with:
recursive: false
@ -35,7 +35,7 @@ jobs:
prerelease: true
title: v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}
files: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
- uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
with:
name: jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
path: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip

View File

@ -12,7 +12,7 @@
##########################################################################
APPNAME = Jellyfin_Roku
VERSION = 1.4.12
VERSION = 1.6.2
ROKU_TEST_ID = 1
ROKU_TEST_WAIT_DURATION = 5

View File

@ -0,0 +1,13 @@
sub init()
m.top.functionName = "getNextEpisodeTask"
end sub
sub getNextEpisodeTask()
m.nextEpisodeData = api_API().shows.getepisodes(m.top.showID, {
UserId: get_setting("active_user"),
StartItemId: m.top.videoID,
Limit: 2
})
m.top.nextEpisodeData = m.nextEpisodeData
end sub

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="GetNextEpisodeTask" extends="Task">
<interface>
<field id="videoID" type="string" />
<field id="showID" type="string" />
<field id="nextEpisodeData" type="assocarray" />
</interface>
<script type="text/brightscript" uri="GetNextEpisodeTask.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
</component>

View File

@ -0,0 +1,42 @@
sub init()
m.itemPoster = m.top.findNode("itemPoster")
m.posterText = m.top.findNode("posterText")
m.posterText.font.size = 30
m.backdrop = m.top.findNode("backdrop")
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
'Parent is MarkupGrid and it's parent is the ItemGrid
m.topParent = m.top.GetParent().GetParent()
'Get the imageDisplayMode for these grid items
if m.topParent.imageDisplayMode <> invalid
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
end if
end sub
sub itemContentChanged()
m.backdrop.blendColor = "#101010"
itemData = m.top.itemContent
if not isValid(itemData) then return
m.itemPoster.uri = itemData.PosterUrl
m.posterText.text = itemData.title
'If Poster not loaded, ensure "blue box" is shown until loaded
if m.itemPoster.loadStatus <> "ready"
m.backdrop.visible = true
m.posterText.visible = true
end if
end sub
'Hide backdrop and text when poster loaded
sub onPosterLoadStatusChanged()
if m.itemPoster.loadStatus = "ready"
m.backdrop.visible = false
m.posterText.visible = false
end if
end sub

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="GridItemSmall" extends="Group">
<children>
<Poster id="backdrop" translation="[0,15]" width="230" height="320" loadDisplayMode="scaleToZoom" uri="pkg:/images/white.9.png" />
<Poster id="itemPoster" translation="[0,15]" width="230" height="320" loadDisplayMode="scaleToZoom" />
<Poster id="itemIcon" width="50" height="50" translation="[230,10]" />
<Label id="posterText" width="230" height="320" translation="[5,5]" horizAlign="center" vertAlign="center" ellipsizeOnBoundary="true" wrap="true" />
</children>
<interface>
<field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
</interface>
<script type="text/brightscript" uri="GridItemSmall.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -320,13 +320,14 @@ end sub
' Set Photo Album view, sort, and filter options
sub setPhotoAlbumOptions(options)
' TODO/FIXME: Show shuffle options once implemented
' options.views = [
' { "Title": tr("Don't Shuffle"), "Name": "singlephoto"}
' { "Title": tr("Shuffle"), "Name": "shufflephoto"}
' ]
options.views = []
options.views = [
{ "Title": tr("Slideshow Off"), "Name": "singlephoto" }
{ "Title": tr("Slideshow On"), "Name": "slideshowphoto" }
{ "Title": tr("Random Off"), "Name": "singlephoto" }
{ "Title": tr("Random On"), "Name": "randomphoto" }
]
options.sort = []
options.filter = []
end sub
' Set Default view, sort, and filter options
@ -574,14 +575,17 @@ sub optionsClosed()
end if
end if
if m.top.parentItem.Type = "CollectionFolder" or m.top.parentItem.CollectionType = "CollectionFolder"
' Did the user just request "Shuffle" on a PhotoAlbum?
if m.top.parentItem.Type = "CollectionFolder" or m.top.parentItem.Type = "Folder" or m.top.parentItem.CollectionType = "CollectionFolder"
' Did the user just request "Random" on a PhotoAlbum?
if m.options.view = "singlephoto"
' TODO/FIXME: Stop shuffling here
print "TODO/FIXME: Stop any shuffling here"
else if m.options.view = "shufflephoto"
' TODO/FIXME: Start shuffling here
print "TODO/FIXME: Start shuffle here"
set_user_setting("photos.slideshow", "false")
set_user_setting("photos.random", "false")
else if m.options.view = "slideshowphoto"
set_user_setting("photos.slideshow", "true")
set_user_setting("photos.random", "false")
else if m.options.view = "randomphoto"
set_user_setting("photos.random", "true")
set_user_setting("photos.slideshow", "false")
end if
end if
@ -724,9 +728,10 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true
else if itemToPlay <> invalid and itemToPlay.type = "Photo"
' Spawn photo player task
photoPlayer = CreateObject("roSgNode", "PhotoPlayerTask")
photoPlayer.itemContent = itemToPlay
photoPlayer.control = "RUN"
photoPlayer = CreateObject("roSgNode", "PhotoDetails")
photoPlayer.items = markupGrid
photoPlayer.itemIndex = markupGrid.itemFocused
m.global.sceneManager.callfunc("pushScene", photoPlayer)
return true
end if
else if key = "left" and topGrp.isinFocusChain()

View File

@ -20,6 +20,16 @@ sub loadItems()
sort_order = "Descending"
end if
if m.top.ItemType = "LogoImage"
logoImageExists = api_API().items.headimageurlbyname(m.top.itemId, "logo")
if logoImageExists
m.top.content = [api_API().items.getimageurl(m.top.itemId, "logo", 0, { "maxHeight": 500, "maxWidth": 500, "quality": "90" })]
else
m.top.content = []
end if
return
end if
params = {
limit: m.top.limit,
@ -117,7 +127,49 @@ sub loadItems()
else if item.type = "Episode"
tmp = CreateObject("roSGNode", "TVEpisode")
else if item.Type = "Genre"
tmp = CreateObject("roSGNode", "FolderData")
tmp = CreateObject("roSGNode", "ContentNode")
tmp.title = item.name
genreData = api_API().users.getitemsbyquery(get_setting("active_user"), {
SortBy: "Random",
SortOrder: "Ascending",
IncludeItemTypes: "Movie",
Recursive: true,
Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo",
ImageTypeLimit: 1,
EnableImageTypes: "Primary",
Limit: 6,
GenreIds: item.id,
EnableTotalRecordCount: false,
ParentId: m.top.itemId
})
if genreData.Items.Count() > 5
' Add View All item to the start of the row
row = tmp.createChild("FolderData")
row.parentFolder = m.top.itemId
genreMovieImage = api_API().items.getimageurl(item.id)
row.title = item.name
row.json = item
row.FHDPOSTERURL = genreMovieImage
row.HDPOSTERURL = genreMovieImage
row.SDPOSTERURL = genreMovieImage
row.type = "Folder"
end if
for each genreMovie in genreData.Items
row = tmp.createChild("MovieData")
genreMovieImage = api_API().items.getimageurl(genreMovie.id)
row.title = genreMovie.name
row.FHDPOSTERURL = genreMovieImage
row.HDPOSTERURL = genreMovieImage
row.SDPOSTERURL = genreMovieImage
row.json = genreMovie
row.id = genreMovie.id
row.type = genreMovie.type
end for
else if item.Type = "Studio"
tmp = CreateObject("roSGNode", "FolderData")
else if item.Type = "MusicAlbum"
@ -135,12 +187,16 @@ sub loadItems()
else
print "[LoadItems] Unknown Type: " item.Type
end if
if tmp <> invalid
tmp.parentFolder = m.top.itemId
tmp.json = item
if item.UserData <> invalid and item.UserData.isFavorite <> invalid
tmp.favorite = item.UserData.isFavorite
if item.Type <> "Genre"
tmp.parentFolder = m.top.itemId
tmp.json = item
if item.UserData <> invalid and item.UserData.isFavorite <> invalid
tmp.favorite = item.UserData.isFavorite
end if
end if
results.push(tmp)
end if
end for

View File

@ -0,0 +1,790 @@
sub setupNodes()
m.options = m.top.findNode("options")
m.itemGrid = m.top.findNode("itemGrid")
m.voiceBox = m.top.findNode("voiceBox")
m.backdrop = m.top.findNode("backdrop")
m.newBackdrop = m.top.findNode("backdropTransition")
m.emptyText = m.top.findNode("emptyText")
m.selectedMovieName = m.top.findNode("selectedMovieName")
m.selectedMovieOverview = m.top.findNode("selectedMovieOverview")
m.selectedMovieProductionYear = m.top.findNode("selectedMovieProductionYear")
m.selectedMovieOfficialRating = m.top.findNode("selectedMovieOfficialRating")
m.movieLogo = m.top.findNode("movieLogo")
m.swapAnimation = m.top.findNode("backroundSwapAnimation")
m.spinner = m.top.findNode("spinner")
m.Alpha = m.top.findNode("AlphaMenu")
m.AlphaSelected = m.top.findNode("AlphaSelected")
m.micButton = m.top.findNode("micButton")
m.micButtonText = m.top.findNode("micButtonText")
m.communityRatingGroup = m.top.findNode("communityRatingGroup")
m.criticRatingIcon = m.top.findNode("criticRatingIcon")
m.criticRatingGroup = m.top.findNode("criticRatingGroup")
m.overhang = m.top.getScene().findNode("overhang")
m.genreList = m.top.findNode("genrelist")
m.infoGroup = m.top.findNode("infoGroup")
end sub
sub init()
setupNodes()
m.overhang.isVisible = false
m.showItemCount = get_user_setting("itemgrid.showItemCount") = "true"
m.swapAnimation.observeField("state", "swapDone")
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.genreData = CreateObject("roSGNode", "ContentNode")
m.genreList.observeField("itemSelected", "onGenreItemSelected")
m.genreList.content = m.genreData
m.itemGrid.observeField("itemFocused", "onItemFocused")
m.itemGrid.observeField("itemSelected", "onItemSelected")
m.itemGrid.observeField("alphaSelected", "onItemalphaSelected")
'Voice filter setup
m.voiceBox.voiceEnabled = true
m.voiceBox.active = true
m.voiceBox.observeField("text", "onvoiceFilter")
'set voice help text
m.voiceBox.hintText = tr("Use voice remote to search")
'backdrop
m.newBackdrop.observeField("loadStatus", "newBGLoaded")
'Background Image Queued for loading
m.queuedBGUri = ""
'Item sort - maybe load defaults from user prefs?
m.sortField = "SortName"
m.sortAscending = true
m.filter = "All"
m.favorite = "Favorite"
m.loadItemsTask = createObject("roSGNode", "LoadItemsTask2")
m.loadLogoTask = createObject("roSGNode", "LoadItemsTask2")
'set inital counts for overhang before content is loaded.
m.loadItemsTask.totalRecordCount = 0
m.spinner.visible = true
'Get reset folder setting
m.resetGrid = get_user_setting("itemgrid.reset") = "true"
'Check if device has voice remote
devinfo = CreateObject("roDeviceInfo")
'Hide voice search if device does not have voice remote
if devinfo.HasFeature("voice_remote") = false
m.micButton.visible = false
m.micButtonText.visible = false
end if
end sub
sub OnScreenHidden()
if not m.overhang.isVisible
m.overhang.disableMoveAnimation = true
m.overhang.isVisible = true
m.overhang.disableMoveAnimation = false
end if
end sub
sub OnScreenShown()
m.overhang.isVisible = false
if m.top.lastFocus <> invalid
m.top.lastFocus.setFocus(true)
else
m.top.setFocus(true)
end if
end sub
'
'Load initial set of Data
sub loadInitialItems()
m.loadItemsTask.control = "stop"
m.spinner.visible = true
if m.top.parentItem.json.Type = "CollectionFolder"
m.top.HomeLibraryItem = m.top.parentItem.Id
end if
if m.top.parentItem.backdropUrl <> invalid
SetBackground(m.top.parentItem.backdropUrl)
else
SetBackground("")
end if
m.sortField = get_user_setting("display." + m.top.parentItem.Id + ".sortField")
sortAscendingStr = get_user_setting("display." + m.top.parentItem.Id + ".sortAscending")
m.filter = get_user_setting("display." + m.top.parentItem.Id + ".filter")
m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing")
if not isValid(m.sortField) then m.sortField = "SortName"
if not isValid(m.filter) then m.filter = "All"
if not isValid(m.view) then m.view = "Movies"
if sortAscendingStr = invalid or sortAscendingStr = "true"
m.sortAscending = true
else
m.sortAscending = false
end if
if m.top.parentItem.json.type = "Studio"
m.loadItemsTask.studioIds = m.top.parentItem.id
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
m.loadItemsTask.genreIds = ""
else if m.top.parentItem.json.type = "Genre"
m.loadItemsTask.genreIds = m.top.parentItem.id
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
m.loadItemsTask.studioIds = ""
else if m.view = "Movies" or m.options.view = "Movies"
m.loadItemsTask.studioIds = ""
m.loadItemsTask.genreIds = ""
else
m.loadItemsTask.itemId = m.top.parentItem.Id
end if
m.loadItemsTask.nameStartsWith = m.top.alphaSelected
m.loadItemsTask.searchTerm = m.voiceBox.text
m.emptyText.visible = false
m.loadItemsTask.sortField = m.sortField
m.loadItemsTask.sortAscending = m.sortAscending
m.loadItemsTask.filter = m.filter
m.loadItemsTask.startIndex = 0
' Load Item Types
if getCollectionType() = "movies"
m.loadItemsTask.itemType = "Movie"
m.loadItemsTask.itemId = m.top.parentItem.Id
end if
' By default we load movies
m.loadItemsTask.studioIds = ""
m.loadItemsTask.view = "Movies"
m.itemGrid.translation = "[96, 650]"
m.itemGrid.numRows = "2"
m.selectedMovieOverview.visible = true
m.infoGroup.visible = true
if m.options.view = "Studios" or m.view = "Studios"
m.itemGrid.translation = "[96, 60]"
m.itemGrid.numRows = "3"
m.loadItemsTask.view = "Networks"
m.top.imageDisplayMode = "scaleToFit"
m.selectedMovieOverview.visible = false
m.infoGroup.visible = false
else if m.options.view = "Genres" or m.view = "Genres"
m.loadItemsTask.StudioIds = m.top.parentItem.Id
m.loadItemsTask.view = "Genres"
m.movieLogo.visible = false
m.selectedMovieName.visible = false
m.selectedMovieOverview.visible = false
m.infoGroup.visible = false
end if
m.loadItemsTask.observeField("content", "ItemDataLoaded")
m.spinner.visible = true
m.loadItemsTask.control = "RUN"
SetUpOptions()
end sub
' Set Movies view, sort, and filter options
sub setMoviesOptions(options)
options.views = [
{ "Title": tr("Movies"), "Name": "Movies" },
{ "Title": tr("Studios"), "Name": "Studios" },
{ "Title": tr("Genres"), "Name": "Genres" }
]
if m.top.parentItem.json.type = "Genre"
options.views = [
{ "Title": tr("Movies"), "Name": "Movies" }
]
end if
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("IMDB_RATING"), "Name": "CommunityRating" },
{ "Title": tr("CRITIC_RATING"), "Name": "CriticRating" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
{ "Title": tr("PLAY_COUNT"), "Name": "PlayCount" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
{ "Title": tr("RUNTIME"), "Name": "Runtime" }
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
]
if m.options.view = "Genres" or m.view = "Genres"
options.sort = []
options.filter = []
end if
if m.options.view = "Studios" or m.view = "Studios"
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
]
end if
end sub
' Return parent collection type
function getCollectionType() as string
if m.top.parentItem.collectionType = invalid
return m.top.parentItem.Type
else
return m.top.parentItem.CollectionType
end if
end function
' Search string array for search value. Return if it's found
function inStringArray(array, searchValue) as boolean
for each item in array
if lcase(item) = lcase(searchValue) then return true
end for
return false
end function
' Data to display when options button selected
sub SetUpOptions()
options = {}
options.filter = []
options.favorite = []
setMoviesOptions(options)
' Set selected view option
for each o in options.views
if o.Name = m.view
o.Selected = true
o.Ascending = m.sortAscending
m.options.view = o.Name
end if
end for
' Set selected sort option
for each o in options.sort
if o.Name = m.sortField
o.Selected = true
o.Ascending = m.sortAscending
m.options.sortField = o.Name
end if
end for
' Set selected filter option
for each o in options.filter
if o.Name = m.filter
o.Selected = true
m.options.filter = o.Name
end if
end for
m.options.options = options
end sub
'
' Logo Image Loaded Event Handler
sub LogoImageLoaded(msg)
data = msg.GetData()
m.loadLogoTask.unobserveField("content")
m.loadLogoTask.content = []
if data.Count() > 0
m.movieLogo.uri = data[0]
m.movieLogo.visible = true
else
m.selectedMovieName.visible = true
end if
end sub
'
'Handle loaded data, and add to Grid
sub ItemDataLoaded(msg)
m.top.alphaActive = false
itemData = msg.GetData()
m.loadItemsTask.unobserveField("content")
m.loadItemsTask.content = []
if itemData = invalid
m.Loading = false
return
end if
if m.loadItemsTask.view = "Genres"
' Reset genre list data
m.genreData.removeChildren(m.genreData.getChildren(-1, 0))
for each item in itemData
m.genreData.appendChild(item)
end for
m.itemGrid.opacity = "0"
m.genreList.opacity = "1"
m.itemGrid.setFocus(false)
m.genreList.setFocus(true)
m.loading = false
m.spinner.visible = false
return
end if
m.itemGrid.opacity = "1"
m.genreList.opacity = "0"
m.itemGrid.setFocus(true)
m.genreList.setFocus(false)
for each item in itemData
m.data.appendChild(item)
end for
'Update the stored counts
m.loadedItems = m.itemGrid.content.getChildCount()
m.loadedRows = m.loadedItems / m.itemGrid.numColumns
m.Loading = false
'If there are no items to display, show message
if m.loadedItems = 0
m.emptyText.text = tr("NO_ITEMS").Replace("%1", m.top.parentItem.Type)
m.emptyText.visible = true
end if
m.spinner.visible = false
end sub
'
'Set Selected Movie Name
sub SetName(movieName as string)
m.selectedMovieName.text = movieName
end sub
'
'Set Selected Movie Overview
sub SetOverview(movieOverview as string)
m.selectedMovieOverview.text = movieOverview
end sub
'
'Set Selected Movie OfficialRating
sub SetOfficialRating(movieOfficialRating as string)
m.selectedMovieOfficialRating.text = movieOfficialRating
end sub
'
'Set Selected Movie ProductionYear
sub SetProductionYear(movieProductionYear)
m.selectedMovieProductionYear.text = movieProductionYear
end sub
'
'Set Background Image
sub SetBackground(backgroundUri as string)
if backgroundUri = ""
m.backdrop.opacity = 0
end if
'If a new image is being loaded, or transitioned to, store URL to load next
if m.swapAnimation.state <> "stopped" or m.newBackdrop.loadStatus = "loading"
m.queuedBGUri = backgroundUri
return
end if
m.newBackdrop.uri = backgroundUri
end sub
'
'Handle new item being focused
sub onItemFocused()
focusedRow = m.itemGrid.currFocusRow
itemInt = m.itemGrid.itemFocused
' If no selected item, set background to parent backdrop
if itemInt = -1
return
end if
m.movieLogo.visible = false
m.selectedMovieName.visible = false
' Load more data if focus is within last 5 rows, and there are more items to load
if focusedRow >= m.loadedRows - 5 and m.loadeditems < m.loadItemsTask.totalRecordCount
loadMoreData()
end if
m.selectedFavoriteItem = getItemFocused()
m.communityRatingGroup.visible = false
m.criticRatingGroup.visible = false
if m.options.view = "Studios" or m.view = "Studios"
return
end if
itemData = m.selectedFavoriteItem.json
if isValid(itemData.communityRating)
setFieldText("communityRating", int(itemData.communityRating * 10) / 10)
m.communityRatingGroup.visible = true
end if
if isValid(itemData.CriticRating)
setFieldText("criticRatingLabel", itemData.criticRating)
tomato = "pkg:/images/rotten.png"
if itemData.CriticRating > 60
tomato = "pkg:/images/fresh.png"
end if
m.criticRatingIcon.uri = tomato
m.criticRatingGroup.visible = true
end if
if isValid(itemData.Name)
SetName(itemData.Name)
else
SetName("")
end if
if isValid(itemData.Overview)
SetOverview(itemData.Overview)
else
SetOverview("")
end if
if isValid(itemData.ProductionYear)
SetProductionYear(str(itemData.ProductionYear))
else
SetProductionYear("")
end if
if type(itemData.RunTimeTicks) = "LongInteger"
setFieldText("runtime", stri(getRuntime(itemData.RunTimeTicks)) + " mins")
else
setFieldText("runtime", "")
end if
if isValid(itemData.OfficialRating)
SetOfficialRating(itemData.OfficialRating)
else
SetOfficialRating("")
end if
m.loadLogoTask.itemId = itemData.id
m.loadLogoTask.itemType = "LogoImage"
m.loadLogoTask.observeField("content", "LogoImageLoaded")
m.loadLogoTask.control = "RUN"
' Set Background to item backdrop
SetBackground(m.selectedFavoriteItem.backdropUrl)
end sub
function getRuntime(runTimeTicks) as integer
return round(runTimeTicks / 600000000.0)
end function
function round(f as float) as integer
' BrightScript only has a "floor" round
' This compares floor to floor + 1 to find which is closer
m = int(f)
n = m + 1
x = abs(f - m)
y = abs(f - n)
if y > x
return m
else
return n
end if
end function
sub setFieldText(field, value)
node = m.top.findNode(field)
if node = invalid or value = invalid then return
' Handle non strings... Which _shouldn't_ happen, but hey
if type(value) = "roInt" or type(value) = "Integer"
value = str(value)
else if type(value) = "roFloat" or type(value) = "Float"
value = str(value)
else if type(value) <> "roString" and type(value) <> "String"
value = ""
end if
node.text = value
end sub
'
'When Image Loading Status changes
sub newBGLoaded()
'If image load was sucessful, start the fade swap
if m.newBackdrop.loadStatus = "ready"
m.swapAnimation.control = "start"
end if
end sub
'
'Swap Complete
sub swapDone()
if m.swapAnimation.state = "stopped"
'Set main BG node image and hide transitioning node
m.backdrop.uri = m.newBackdrop.uri
m.backdrop.opacity = 1
m.newBackdrop.opacity = 0
'If there is another one to load
if m.newBackdrop.uri <> m.queuedBGUri and m.queuedBGUri <> ""
SetBackground(m.queuedBGUri)
m.queuedBGUri = ""
end if
end if
end sub
'
'Load next set of items
sub loadMoreData()
m.spinner.visible = true
if m.Loading = true then return
m.Loading = true
m.loadItemsTask.startIndex = m.loadedItems
m.loadItemsTask.observeField("content", "ItemDataLoaded")
m.loadItemsTask.control = "RUN"
end sub
'
'Item Selected
sub onItemSelected()
m.top.selectedItem = m.itemGrid.content.getChild(m.itemGrid.itemSelected)
end sub
'
'Returns Focused Item
function getItemFocused()
return m.itemGrid.content.getChild(m.itemGrid.itemFocused)
end function
'
'Genre Item Selected
sub onGenreItemSelected()
m.top.selectedItem = m.genreList.content.getChild(m.genreList.rowItemSelected[0]).getChild(m.genreList.rowItemSelected[1])
end sub
sub onItemalphaSelected()
if m.top.alphaSelected <> ""
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.genreData = CreateObject("roSGNode", "ContentNode")
m.genreList.content = m.genreData
m.loadItemsTask.searchTerm = ""
m.VoiceBox.text = ""
m.loadItemsTask.nameStartsWith = m.alpha.itemAlphaSelected
m.spinner.visible = true
loadInitialItems()
end if
end sub
sub onvoiceFilter()
if m.VoiceBox.text <> ""
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.top.alphaSelected = ""
m.loadItemsTask.NameStartsWith = " "
m.loadItemsTask.searchTerm = m.voiceBox.text
m.loadItemsTask.recursive = true
m.spinner.visible = true
loadInitialItems()
end if
end sub
'
'Check if options updated and any reloading required
sub optionsClosed()
reload = false
if m.options.sortField <> m.sortField or m.options.sortAscending <> m.sortAscending
m.sortField = m.options.sortField
m.sortAscending = m.options.sortAscending
reload = true
sortAscendingStr = "true"
'Store sort settings
if not m.sortAscending
sortAscendingStr = "false"
end if
set_user_setting("display." + m.top.parentItem.Id + ".sortField", m.sortField)
set_user_setting("display." + m.top.parentItem.Id + ".sortAscending", sortAscendingStr)
end if
if m.options.filter <> m.filter
m.filter = m.options.filter
reload = true
set_user_setting("display." + m.top.parentItem.Id + ".filter", m.options.filter)
end if
m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing")
if m.options.view <> m.view
m.view = m.options.view
set_user_setting("display." + m.top.parentItem.Id + ".landing", m.view)
' Reset any filtering or search terms
m.top.alphaSelected = ""
m.loadItemsTask.NameStartsWith = " "
m.loadItemsTask.searchTerm = ""
m.filter = "All"
m.sortField = "SortName"
m.sortAscending = true
' Reset view to defaults
set_user_setting("display." + m.top.parentItem.Id + ".sortField", m.sortField)
set_user_setting("display." + m.top.parentItem.Id + ".sortAscending", "true")
set_user_setting("display." + m.top.parentItem.Id + ".filter", m.filter)
reload = true
end if
if reload
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
end if
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
end sub
sub onChannelSelected(msg)
node = msg.getRoSGNode()
m.top.lastFocus = lastFocusedChild(node)
if node.watchChannel <> invalid
' Clone the node when it's reused/update in the TimeGrid it doesn't automatically start playing
m.top.selectedItem = node.watchChannel.clone(false)
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "left" and m.voiceBox.isinFocusChain()
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
m.voiceBox.setFocus(false)
end if
if key = "options"
if m.options.visible = true
m.options.visible = false
m.top.removeChild(m.options)
optionsClosed()
else
itemSelected = m.selectedFavoriteItem
if itemSelected <> invalid
m.options.selectedFavoriteItem = itemSelected
end if
m.options.visible = true
m.top.appendChild(m.options)
m.options.setFocus(true)
end if
return true
else if key = "back"
if m.options.visible = true
m.options.visible = false
optionsClosed()
return true
else
m.global.sceneManager.callfunc("popScene")
m.loadItemsTask.control = "stop"
return true
end if
else if key = "play" or key = "OK"
itemToPlay = getItemFocused()
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
m.top.quickPlayNode = itemToPlay
return true
end if
else if key = "left"
if m.itemGrid.isinFocusChain()
m.top.alphaActive = true
m.itemGrid.setFocus(false)
alpha = m.alpha.getChild(0).findNode("Alphamenu")
alpha.setFocus(true)
return true
else if m.genreList.isinFocusChain()
m.top.alphaActive = true
m.genreList.setFocus(false)
alpha = m.alpha.getChild(0).findNode("Alphamenu")
alpha.setFocus(true)
return true
end if
else if key = "right" and m.Alpha.isinFocusChain()
m.top.alphaActive = false
m.Alpha.setFocus(false)
m.Alpha.visible = true
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
return true
else if key = "replay" and m.itemGrid.isinFocusChain()
if m.resetGrid = true
m.itemGrid.animateToItem = 0
else
m.itemGrid.jumpToItem = 0
end if
else if key = "replay" and m.genreList.isinFocusChain()
if m.resetGrid = true
m.genreList.animateToItem = 0
else
m.genreList.jumpToItem = 0
end if
return true
end if
if key = "replay"
m.spinner.visible = true
m.loadItemsTask.searchTerm = ""
m.loadItemsTask.nameStartsWith = ""
m.voiceBox.text = ""
m.top.alphaSelected = ""
m.loadItemsTask.filter = "All"
m.filter = "All"
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
return true
end if
return false
end function

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="MovieLibraryView" extends="JFScreen">
<children>
<Rectangle id="screenSaverBackground" width="1920" height="1080" color="#000000" />
<VoiceTextEditBox id="VoiceBox" visible="true" width = "40" translation = "[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x000000ff" translation = "[25, 75]" />
<maskGroup translation="[820, 0]" id="backgroundMask" maskUri="pkg:/images/backgroundmask.png" maskSize="[1220,700]">
<poster id="backdrop" loadDisplayMode="scaleToFill" width="1100" height="700" opacity="1" />
<poster id="backdropTransition" loadDisplayMode="scaleToFill" width="1100" height="700" opacity="1" />
</maskGroup>
<Label id="selectedMovieName" visible="false" translation="[120, 40]" wrap="true" font="font:LargeBoldSystemFont" width="850" height="196" horizAlign="left" vertAlign="center" />
<Poster id="movieLogo" visible="false" translation="[120, 40]" loadDisplayMode="scaleToFit" width="384" height="196" />
<LayoutGroup layoutDirection="horiz" translation="[120, 270]" itemSpacings="[30]" id="infoGroup">
<Label id="selectedMovieProductionYear" font="font:SmallestSystemFont" />
<Label id="runtime" font="font:SmallestSystemFont" />
<Label id="selectedMovieOfficialRating" font="font:SmallestSystemFont" />
<LayoutGroup id="communityRatingGroup" visible="false" layoutDirection="horiz" itemSpacings="[-5]">
<Poster id="star" uri="pkg:/images/sharp_star_white_18dp.png" height="28" width="28" blendColor="#00a4dcFF" />
<Label id="communityRating" font="font:SmallestSystemFont" />
</LayoutGroup>
<LayoutGroup layoutDirection="horiz" id="criticRatingGroup">
<Poster id="criticRatingIcon" height="28" width="28" />
<Label id="criticRatingLabel" font="font:SmallestSystemFont" />
</LayoutGroup>
</LayoutGroup>
<Label id="selectedMovieOverview" font="font:SmallestSystemFont" translation="[120, 360]" wrap="true" lineSpacing="20" maxLines="5" width="850" ellipsisText="..." />
<MarkupGrid id="itemGrid" itemComponentName="GridItemSmall" numColumns="7" numRows="2" vertFocusAnimationStyle="fixed" itemSize="[230, 310]" itemSpacing="[20, 20]" />
<RowList opacity="0" id="genrelist" translation="[120, 60]" showRowLabel="true" itemComponentName="GridItemSmall" numColumns="1" numRows="3" vertFocusAnimationStyle="fixed" itemSize = "[1900, 360]" rowItemSize="[ [230, 320] ]" rowItemSpacing="[ [20, 0] ]" itemSpacing="[0, 60]" />
<Label id="micButtonText" font="font:SmallSystemFont" visible="false" />
<Button id = "micButton" maxWidth = "20" translation = "[20, 120]" iconUri = "pkg:/images/icons/mic_icon.png"/>
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear">
<FloatFieldInterpolator id = "fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 1.00 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id = "fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 1.00, 0.00 ]" fieldToInterp="backdrop.opacity" />
</Animation>
<Alpha id="AlphaMenu" />
</children>
<interface>
<field id="HomeLibraryItem" type="string"/>
<field id="parentItem" type="node" onChange="loadInitialItems" />
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
<field id="imageDisplayMode" type="string" value="scaleToZoom" />
<field id="AlphaSelected" type="string" alias="AlphaMenu.itemAlphaSelected" alwaysNotify="true" onChange="onItemAlphaSelected" />
<field id="alphaActive" type="boolean" value="false" />
<field id="jumpToItem" type="integer" value="" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
<script type="text/brightscript" uri="MovieLibraryView.brs" />
</component>

View File

@ -25,19 +25,14 @@ sub init()
' get system preference clock format (12/24hr)
di = CreateObject("roDeviceInfo")
m.clockFormat = di.GetClockFormat()
' grab current time
currentTime = CreateObject("roDateTime")
currentTime.ToLocalTime()
m.currentHours = currentTime.GetHours()
m.currentMinutes = currentTime.GetMinutes()
m.overlayHours = m.top.findNode("overlayHours")
m.overlayMinutes = m.top.findNode("overlayMinutes")
m.overlayMeridian = m.top.findNode("overlayMeridian")
' start timer
m.currentTimeTimer = m.top.findNode("currentTimeTimer")
m.currentTimeTimer.control = "start"
m.currentTimeTimer.ObserveField("fire", "updateTime")
updateTimeDisplay()
end if
setClockVisibility()
@ -97,64 +92,50 @@ sub updateUser()
end sub
sub updateTime()
if (m.currentMinutes + 1) > 59
m.currentHours = m.currentHours + 1
m.currentMinutes = 0
else
m.currentMinutes = m.currentMinutes + 1
end if
m.currentTime = CreateObject("roDateTime")
m.currentTime.ToLocalTime()
m.currentTimeTimer.duration = 60 - m.currentTime.GetSeconds()
m.currentHours = m.currentTime.GetHours()
m.currentMinutes = m.currentTime.GetMinutes()
updateTimeDisplay()
end sub
sub resetTime()
m.currentTimeTimer.control = "stop"
currentTime = CreateObject("roDateTime")
m.currentTimeTimer.control = "start"
currentTime.ToLocalTime()
m.currentHours = currentTime.GetHours()
m.currentMinutes = currentTime.GetMinutes()
updateTimeDisplay()
updateTime()
end sub
sub updateTimeDisplay()
overlayHours = m.top.findNode("overlayHours")
overlayMinutes = m.top.findNode("overlayMinutes")
overlayMeridian = m.top.findNode("overlayMeridian")
if m.clockFormat = "24h"
overlayMeridian.text = ""
m.overlayMeridian.text = ""
if m.currentHours < 10
overlayHours.text = "0" + StrI(m.currentHours).trim()
m.overlayHours.text = "0" + StrI(m.currentHours).trim()
else
overlayHours.text = m.currentHours
m.overlayHours.text = m.currentHours
end if
else
if m.currentHours < 12
overlayMeridian.text = "AM"
m.overlayMeridian.text = "AM"
if m.currentHours = 0
overlayHours.text = "12"
m.overlayHours.text = "12"
else
overlayHours.text = m.currentHours
m.overlayHours.text = m.currentHours
end if
else
overlayMeridian.text = "PM"
m.overlayMeridian.text = "PM"
if m.currentHours = 12
overlayHours.text = "12"
m.overlayHours.text = "12"
else
overlayHours.text = m.currentHours - 12
m.overlayHours.text = m.currentHours - 12
end if
end if
end if
if m.currentMinutes < 10
overlayMinutes.text = "0" + StrI(m.currentMinutes).trim()
m.overlayMinutes.text = "0" + StrI(m.currentMinutes).trim()
else
overlayMinutes.text = m.currentMinutes
m.overlayMinutes.text = m.currentMinutes
end if
end sub

View File

@ -2,6 +2,8 @@ sub init()
m.playbackTimer = m.top.findNode("playbackTimer")
m.bufferCheckTimer = m.top.findNode("bufferCheckTimer")
m.top.observeField("state", "onState")
m.top.observeField("content", "onContentChange")
m.playbackTimer.observeField("fire", "ReportPlayback")
m.bufferPercentage = 0 ' Track whether content is being loaded
m.playReported = false
@ -12,6 +14,88 @@ sub init()
clockNode = findNodeBySubtype(m.top, "clock")
if clockNode[0] <> invalid then clockNode[0].parent.removeChild(clockNode[0].node)
end if
'Play Next Episode button
m.nextEpisodeButton = m.top.findNode("nextEpisode")
m.nextEpisodeButton.text = tr("Next Episode")
m.nextEpisodeButton.setFocus(false)
m.showNextEpisodeButtonAnimation = m.top.findNode("showNextEpisodeButton")
m.hideNextEpisodeButtonAnimation = m.top.findNode("hideNextEpisodeButton")
m.checkedForNextEpisode = false
m.getNextEpisodeTask = createObject("roSGNode", "GetNextEpisodeTask")
m.getNextEpisodeTask.observeField("nextEpisodeData", "onNextEpisodeDataLoaded")
end sub
' Event handler for when video content field changes
sub onContentChange()
if not isValid(m.top.content) then return
m.top.observeField("position", "onPositionChanged")
' If video content type is not episode, remove position observer
if m.top.content.contenttype <> 4
m.top.unobserveField("position")
end if
end sub
sub onNextEpisodeDataLoaded()
m.checkedForNextEpisode = true
m.top.observeField("position", "onPositionChanged")
if m.getNextEpisodeTask.nextEpisodeData.Items.count() <> 2
m.top.unobserveField("position")
end if
end sub
'
' Runs Next Episode button animation and sets focus to button
sub showNextEpisodeButton()
if not m.nextEpisodeButton.visible
m.showNextEpisodeButtonAnimation.control = "start"
m.nextEpisodeButton.setFocus(true)
m.nextEpisodeButton.visible = true
end if
end sub
'
'Update count down text
sub updateCount()
m.nextEpisodeButton.text = tr("Next Episode") + " " + Int(m.top.runTime - m.top.position).toStr()
end sub
'
' Runs hide Next Episode button animation and sets focus back to video
sub hideNextEpisodeButton()
m.hideNextEpisodeButtonAnimation.control = "start"
m.nextEpisodeButton.setFocus(false)
m.top.setFocus(true)
end sub
' Checks if we need to display the Next Episode button
sub checkTimeToDisplayNextEpisode()
if int(m.top.position) >= (m.top.runTime - 30)
showNextEpisodeButton()
updateCount()
return
end if
if m.nextEpisodeButton.visible or m.nextEpisodeButton.hasFocus()
m.nextEpisodeButton.visible = false
m.nextEpisodeButton.setFocus(false)
end if
end sub
' When Video Player state changes
sub onPositionChanged()
' Check if dialog is open
m.dialog = m.top.getScene().findNode("dialogBackground")
if not isValid(m.dialog)
checkTimeToDisplayNextEpisode()
end if
end sub
'
@ -40,6 +124,16 @@ sub onState(msg)
m.top.control = "stop"
m.top.backPressed = true
else if m.top.state = "playing"
' Check if next episde is available
if isValid(m.top.showID)
if m.top.showID <> "" and not m.checkedForNextEpisode and m.top.content.contenttype = 4
m.getNextEpisodeTask.showID = m.top.showID
m.getNextEpisodeTask.videoID = m.top.id
m.getNextEpisodeTask.control = "RUN"
end if
end if
if m.playReported = false
ReportPlayback("start")
m.playReported = true
@ -126,12 +220,24 @@ sub dialogClosed(msg)
sourceNode.close = true
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if key = "OK" and m.nextEpisodeButton.hasfocus() and m.top.trickPlayMode = "play"
m.top.state = "finished"
hideNextEpisodeButton()
return true
else
'Hide Next Episode Button
if m.nextEpisodeButton.visible or m.nextEpisodeButton.hasFocus()
m.nextEpisodeButton.visible = false
m.nextEpisodeButton.setFocus(false)
m.top.setFocus(true)
end if
end if
if not press then return false
if m.top.Subtitles.count() and key = "down"
if key = "down"
m.top.selectSubtitlePressed = true
return true
else if key = "up"

View File

@ -22,12 +22,24 @@
<field id="videoId" type="string" />
<field id="mediaSourceId" type="string" />
<field id="audioIndex" type="integer" />
<field id="runTime" type="integer" />
</interface>
<script type="text/brightscript" uri="JFVideo.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
<children>
<timer id="playbackTimer" repeat="true" duration="30" />
<timer id="bufferCheckTimer" repeat="true" />
<JFButton id="nextEpisode" opacity="0" textColor="#f0f0f0" focusedTextColor="#202020" focusFootprintBitmapUri="pkg:/images/option-menu-bg.9.png" focusBitmapUri="pkg:/images/white.9.png" translation="[1500, 900]" />
<!--animation for the play next episode button-->
<Animation id="showNextEpisodeButton" duration="1.0" repeat="false" easeFunction="inQuad">
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[0.0, .9]" fieldToInterp="nextEpisode.opacity" />
</Animation>
<Animation id="hideNextEpisodeButton" duration=".2" repeat="false" easeFunction="inQuad">
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[.9, 0]" fieldToInterp="nextEpisode.opacity" />
</Animation>
</children>
</component>

View File

@ -8,11 +8,13 @@ sub setFields()
m.top.watched = json.UserData.played
m.top.Type = "Movie"
if json.MediaSourceCount <> invalid and json.MediaSourceCount > 1
m.top.mediaSources = []
for each source in json.MediaSources
m.top.mediaSources.push(source)
end for
if isValid(json.MediaSourceCount) and json.MediaSourceCount > 1
if isValid(json.MediaSources)
m.top.mediaSources = []
for each source in json.MediaSources
m.top.mediaSources.push(source)
end for
end if
end if
if json.ProductionYear <> invalid
@ -49,9 +51,11 @@ sub setPoster()
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280 }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
if m.top.json.BackdropImageTags <> invalid
if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280 }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if
end if
end if

View File

@ -12,4 +12,5 @@
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component>

View File

@ -146,7 +146,13 @@ end sub
'
' Clear previous scene from group stack
sub clearPreviousScene()
m.groups.Delete(2)
m.groups.pop()
end sub
'
' Delete scene from group stack at passed index
sub deleteSceneAtIndex(index = 1)
m.groups.Delete(index)
end sub
'

View File

@ -133,6 +133,7 @@ sub itemContentChanged()
end if
return
end if
if itemData.type = "Series"
m.itemText.text = itemData.name
@ -170,6 +171,34 @@ sub itemContentChanged()
return
end if
if itemData.type = "MusicArtist"
m.itemText.text = itemData.name
m.itemTextExtra.text = itemData.json.AlbumArtist
m.itemPoster.uri = ImageURL(itemData.id)
return
end if
if itemData.type = "Audio"
m.itemText.text = itemData.name
m.itemTextExtra.text = itemData.json.AlbumArtist
m.itemPoster.uri = ImageURL(itemData.id)
return
end if
if itemData.type = "TvChannel"
m.itemText.text = itemData.name
m.itemTextExtra.text = itemData.json.AlbumArtist
m.itemPoster.uri = ImageURL(itemData.id)
return
end if
if itemData.type = "Season"
m.itemText.text = itemData.json.SeriesName
m.itemTextExtra.text = itemData.name
m.itemPoster.uri = ImageURL(itemData.id)
return
end if
print "Unhandled Home Item Type: " + itemData.type
end sub

View File

@ -19,13 +19,19 @@ sub init()
' Load the Libraries from API via task
m.LoadLibrariesTask = createObject("roSGNode", "LoadItemsTask")
m.LoadLibrariesTask.observeField("content", "onLibrariesLoaded")
' set up tesk nodes for other rows
m.LoadContinueTask = createObject("roSGNode", "LoadItemsTask")
m.LoadContinueTask.itemsToLoad = "continue"
m.LoadNextUpTask = createObject("roSGNode", "LoadItemsTask")
m.LoadNextUpTask.itemsToLoad = "nextUp"
m.LoadOnNowTask = createObject("roSGNode", "LoadItemsTask")
m.LoadOnNowTask.itemsToLoad = "onNow"
m.LoadFavoritesTask = createObject("roSGNode", "LoadItemsTask")
m.LoadFavoritesTask.itemsToLoad = "favorites"
end sub
sub loadLibraries()
@ -61,20 +67,28 @@ sub onLibrariesLoaded()
continueRow.title = tr("Continue Watching")
nextUpRow = content.CreateChild("HomeRow")
nextUpRow.title = tr("Next Up >")
favoritesRow = content.CreateChild("HomeRow")
favoritesRow.title = tr("Favorites")
sizeArray = [
[464, 311], ' My Media
[464, 331], ' Continue Watching
[464, 331] ' Next Up
[464, 331], ' Next Up
[464, 331] ' Favorites
]
haveLiveTV = false
' validate library data
if m.libraryData <> invalid and m.libraryData.count() > 0
userConfig = m.top.userConfig
' populate My Media row
filteredMedia = filterNodeArray(m.libraryData, "id", userConfig.MyMediaExcludes)
for each item in filteredMedia
mediaRow.appendChild(item)
end for
' create a "Latest In" row for each library
filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes)
for each lib in filteredLatest
@ -99,6 +113,10 @@ sub onLibrariesLoaded()
m.LoadContinueTask.observeField("content", "updateContinueItems")
m.LoadContinueTask.control = "RUN"
' Load the Favorites Data
m.LoadFavoritesTask.observeField("content", "updateFavoritesItems")
m.LoadFavoritesTask.control = "RUN"
' If we have Live TV access, load "On Now" data
if haveLiveTV
m.LoadOnNowTask.observeField("content", "updateOnNowItems")
@ -116,6 +134,51 @@ sub updateHomeRows()
m.LoadContinueTask.control = "RUN"
end sub
sub updateFavoritesItems()
itemData = m.LoadFavoritesTask.content
m.LoadFavoritesTask.unobserveField("content")
m.LoadFavoritesTask.content = []
if itemData = invalid then return
homeRows = m.top.content
rowIndex = getRowIndex("Favorites")
if itemData.count() < 1
if rowIndex <> invalid
' remove the row
deleteFromSizeArray(rowIndex)
homeRows.removeChildIndex(rowIndex)
end if
else
' remake row using the new data
row = CreateObject("roSGNode", "HomeRow")
row.title = tr("Favorites")
itemSize = [464, 331]
for each item in itemData
usePoster = true
if lcase(item.type) = "episode" or lcase(item.type) = "audio" or lcase(item.type) = "musicartist"
usePoster = false
end if
item.usePoster = usePoster
item.imageWidth = row.imageWidth
row.appendChild(item)
end for
if rowIndex = invalid
' insert new row under "My Media"
updateSizeArray(itemSize, 1)
homeRows.insertChild(row, 1)
else
' replace the old row
homeRows.replaceChild(row, rowIndex)
end if
end if
end sub
sub updateContinueItems()
itemData = m.LoadContinueTask.content
m.LoadContinueTask.unobserveField("content")

View File

@ -100,6 +100,27 @@ sub loadItems()
end if
end for
else if m.top.itemsToLoad = "favorites"
url = Substitute("Users/{0}/Items", get_setting("active_user"))
params = {}
params["Filters"] = "IsFavorite"
params["Limit"] = 20
params["recursive"] = true
params["sortby"] = "random"
resp = APIRequest(url, params)
data = getJson(resp)
for each item in data.Items
' Skip Books for now as we don't support it (issue #558)
if item.Type <> "Book"
tmp = CreateObject("roSGNode", "HomeData")
tmp.json = item
results.push(tmp)
end if
end for
else if m.top.itemsToLoad = "onNow"
url = "LiveTv/Programs/Recommended"
params = {}

View File

@ -288,6 +288,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
gridGrp.setFocus(true)
return true
else if key = "back"
m.LoadChannelsTask.control = "stop"
m.global.sceneManager.callFunc("popScene")
return true
end if

View File

@ -103,10 +103,6 @@ sub itemContentChanged()
setFieldText("director", tr("Director") + ": " + directors.join(", "))
end if
if itemData.mediaStreams[0] <> invalid
setFieldText("video_codec", tr("Video") + ": " + itemData.mediaStreams[0].displayTitle)
end if
if get_user_setting("ui.details.hidetagline") = "false"
if itemData.taglines.count() > 0
setFieldText("tagline", itemData.taglines[0])
@ -132,11 +128,28 @@ end sub
sub SetUpVideoOptions(streams)
videos = []
codecDetailsSet = false
for i = 0 to streams.Count() - 1
if streams[i].VideoType = "VideoFile"
codec = ""
if streams[i].mediaStreams <> invalid and streams[i].mediaStreams.Count() > 0 then codec = streams[i].mediaStreams[0].displayTitle
if streams[i].mediaStreams <> invalid and streams[i].mediaStreams.Count() > 0
' find the first (default) video track to get the codec for the details screen
if codecDetailsSet = false
for index = 0 to streams[i].mediaStreams.Count() - 1
if streams[i].mediaStreams[index].Type = "Video"
setFieldText("video_codec", tr("Video") + ": " + streams[i].mediaStreams[index].displayTitle)
codecDetailsSet = true
exit for
end if
end for
end if
codec = streams[i].mediaStreams[0].displayTitle
end if
' Create options for user to switch between video tracks
videos.push({
"Title": streams[i].Name,
"Description": tr("Video"),

View File

@ -408,7 +408,7 @@ sub onMetaDataLoaded()
if data <> invalid and data.count() > 0
' Use metadata to load backdrop image
if isvalid(data.json)
if isValid(data.json)
if isValid(data.json.ArtistItems)
if data.json.ArtistItems.count() > 0
if isValid(data.json.ArtistItems[0].id)

View File

@ -1,21 +1,123 @@
sub init()
m.top.optionsAvailable = false ' Change once Shuffle option is added
m.top.optionsAvailable = true
m.top.overhangVisible = false
m.slideshowTimer = m.top.findNode("slideshowTimer")
m.slideshowTimer.observeField("fire", "nextSlide")
m.status = m.top.findNode("status")
m.textBackground = m.top.findNode("background")
m.statusTimer = m.top.findNode("statusTimer")
m.statusTimer.observeField("fire", "statusUpdate")
m.slideshow = get_user_setting("photos.slideshow")
m.random = get_user_setting("photos.random")
m.showStatusAnimation = m.top.findNode("showStatusAnimation")
m.hideStatusAnimation = m.top.findNode("hideStatusAnimation")
itemContentChanged()
end sub
sub itemContentChanged()
m.LoadLibrariesTask = createObject("roSGNode", "LoadPhotoTask")
m.LoadLibrariesTask.itemContent = m.top.itemContent
m.LoadLibrariesTask.observeField("results", "onPhotoLoaded")
m.LoadLibrariesTask.control = "RUN"
if isValidToContinue(m.top.itemIndex)
m.LoadLibrariesTask = createObject("roSGNode", "LoadPhotoTask")
itemContent = m.top.items.content.getChild(m.top.itemIndex)
m.LoadLibrariesTask.itemContent = itemContent
m.LoadLibrariesTask.observeField("results", "onPhotoLoaded")
m.LoadLibrariesTask.control = "RUN"
end if
end sub
sub onPhotoLoaded()
if m.LoadLibrariesTask.results <> invalid
photo = m.top.findNode("photo")
photo.uri = m.LoadLibrariesTask.results
if m.slideshow = "true" or m.random = "true"
' user has requested either a slideshow or random...
m.slideshowTimer.control = "start"
end if
else
'Show user error here (for example if it's not a supported image type)
message_dialog("This image type is not supported.")
end if
end sub
sub nextSlide()
m.slideshowTimer.control = "stop"
if m.slideshow = "true"
if isValidToContinue(m.top.itemIndex + 1)
m.top.itemIndex++
m.slideshowTimer.control = "start"
end if
else if m.random = "true"
index = rnd(m.top.items.content.getChildCount() - 1)
if isValidToContinue(index)
m.top.itemIndex = index
m.slideshowTimer.control = "start"
end if
end if
end sub
sub statusUpdate()
m.statusTimer.control = "stop"
m.hideStatusAnimation.control = "start"
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "right"
if isValidToContinue(m.top.itemIndex + 1)
m.slideshowTimer.control = "stop"
m.top.itemIndex++
end if
return true
end if
if key = "left"
if isValidToContinue(m.top.itemIndex - 1)
m.slideshowTimer.control = "stop"
m.top.itemIndex--
end if
return true
end if
if key = "play"
if m.slideshowTimer.control = "start"
' stop the slideshow if the user hits "pause"
m.slideshowTimer.control = "stop"
m.status.text = tr("Slideshow Paused")
if m.textBackground.opacity = 0
m.showStatusAnimation.control = "start"
end if
m.statusTimer.control = "start"
else
' start the slideshow if the user hits "play"
m.status.text = tr("Slideshow Resumed")
if m.textBackground.opacity = 0
m.showStatusAnimation.control = "start"
end if
m.slideshow = "true"
m.statusTimer.control = "start"
m.slideshowTimer.control = "start"
end if
return true
end if
if key = "options"
' Options (random etc) is done on itemGrid
return true
end if
return false
end function
function isValidToContinue(index as integer)
if isValid(m.top.items) and isValid(m.top.items.content)
if index >= 0 and index < m.top.items.content.getChildCount()
return true
end if
end if
return false
end function

View File

@ -1,13 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="PhotoDetails" extends="JFGroup">
<children>
<LayoutGroup id="toplevel">
<Poster id="photo" width="1920" height="1080" loadDisplayMode="scaleToFit"/>
</LayoutGroup>
<Poster id="photo" width="1920" height="1080" loadDisplayMode="scaleToFit"/>
<Rectangle id="background" color="0x101010EE" height="120" width="500" Translation="[700, -150]" opacity="0">
<Label id="status" font="font:MediumSystemFont" height="100" width="500" horizAlign="center" vertAlign="bottom"/>
</Rectangle>
<Timer id="slideshowTimer" duration="5" repeat="false" />
<Timer id="statusTimer" duration="2" repeat="false" />
<Animation id="showStatusAnimation" duration="1" repeat="false">
<FloatFieldInterpolator key="[0.0, 0.1]" keyValue="[0, 1]" fieldToInterp="background.opacity" />
<Vector2DFieldInterpolator key="[0.1, 1]" keyValue="[[700, -150], [700, -5]]" fieldToInterp="background.translation" />
</Animation>
<Animation id="hideStatusAnimation" duration="1" repeat="false">
<Vector2DFieldInterpolator key="[0.0, 0.9]" keyValue="[[700, -5], [700, -150]]" fieldToInterp="background.translation" />
<FloatFieldInterpolator key="[0.9, 1]" keyValue="[1, 0]" fieldToInterp="background.opacity" />
</Animation>
</children>
<interface>
<field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="items" type="node" />
<field id="itemIndex" type="integer" value="-1" onChange="itemContentChanged" />
</interface>
<script type="text/brightscript" uri="PhotoDetails.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -1,17 +0,0 @@
sub init()
m.top.functionName = "loadItems"
end sub
sub loadItems()
item = m.top.itemContent
group = CreateObject("roSGNode", "PhotoDetails")
group.optionsAvailable = false
m.global.sceneManager.callFunc("pushScene", group)
group.itemContent = item
' TODO/FIXME:
' Wait some time and move to the next photo...
end sub

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="PhotoPlayerTask" extends="Task">
<interface>
<field id="itemContent" type="node" />
</interface>
<script type="text/brightscript" uri="PhotoPlayerTask.brs" />
</component>

View File

@ -48,7 +48,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.searchAlphabox.textEditBox.translation = "[0, 0]"
end if
if key = "left" and m.searchSelect.isinFocusChain() and (m.searchSelect.currFocusColumn = -1 or m.searchSelect.currFocusColumn = 0)
if key = "left" and m.searchSelect.isinFocusChain()
m.searchAlphabox.setFocus(true)
return true
else if key = "right"

View File

@ -36,8 +36,14 @@ sub itemContentChanged()
m.poster.uri = imageUrl
if type(itemData.RunTimeTicks) = "LongInteger"
m.top.findNode("runtime").text = stri(getRuntime()).trim() + " mins"
if type(itemData.RunTimeTicks) = "roInt" or type(itemData.RunTimeTicks) = "LongInteger"
runTime = getRuntime()
if runTime < 2
m.top.findNode("runtime").text = "1 min"
else
m.top.findNode("runtime").text = stri(runTime).trim() + " mins"
end if
if get_user_setting("ui.design.hideclock") <> "true"
m.top.findNode("endtime").text = tr("Ends at %1").Replace("%1", getEndTime())
end if

View File

@ -3648,5 +3648,9 @@
<translation>Úroveň</translation>
<extracomment>Video profile level</extracomment>
</message>
<message>
<source>Save Credentials?</source>
<translation>Uložit přihlašovací údaje?</translation>
</message>
</context>
</TS>

View File

@ -6,7 +6,7 @@
<name>default</name>
<message>
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
<translation>192.168.1.100:8096 or https://example.com/jellyfin</translation>
<translation>default192.168.1.100:8096 or https://example.com/jellyfin</translation>
</message>
<message>
<source>Cancel</source>
@ -1666,5 +1666,454 @@
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>Version</source>
<translation>Version</translation>
</message>
<message>
<source>Item Titles</source>
<translation>Item Titles</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>Born</source>
<translation>Born</translation>
</message>
<message>
<source>Age</source>
<translation>Age</translation>
</message>
<message>
<source>More Like This</source>
<translation>More Like This</translation>
</message>
<message>
<source>Special Features</source>
<translation>Special Features</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
</message>
<message>
<source>TV Shows</source>
<translation>TV Shows</translation>
</message>
<message>
<source>View Channel</source>
<translation>View Channel</translation>
</message>
<message>
<source>Record</source>
<translation>Record</translation>
</message>
<message>
<source>Record Series</source>
<translation>Record Series</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Cancel Recording</translation>
</message>
<message>
<source>Unknown</source>
<translation>Unknown</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Enter the server name or IP address</source>
<translation>Enter the server name or IP address</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Cast &amp; Crew</translation>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Select an available Jellyfin server from your local network:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Error Getting Playback Information</source>
<translation>Error Getting Playback Information</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>An error was encountered while playing this item. Server did not provide required transcoding data.</translation>
<extracomment>Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url</extracomment>
</message>
<message>
<source>Playback</source>
<translation>Playback</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>MPEG-2 Support</source>
<translation>MPEG-2 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
<translation>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>User Interface</source>
<translation>User Interface</translation>
<extracomment>Title for User Interface section in user setting screen.</extracomment>
</message>
<message>
<source>Media Grid</source>
<translation>Media Grid</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message>
<message>
<source>Media Grid options.</source>
<translation>Media Grid options.</translation>
</message>
<message>
<source>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</source>
<translation>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Item Count</source>
<translation>Item Count</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Show item count in the library and index of selected item.</source>
<translation>Show item count in the library and index of selected item.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Set Favorite</source>
<translation>Set Favourite</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
</message>
<message>
<source>Set Watched</source>
<translation>Set Watched</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>Go to series</source>
<translation>Go to series</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
</message>
<message>
<source>Delete Saved</source>
<translation>Delete Saved</translation>
</message>
<message>
<source>Movies</source>
<translation>Films</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Cancel Series Recording</translation>
</message>
<message>
<source>Close</source>
<translation>Close</translation>
</message>
<message>
<source>On Now</source>
<translation>On Now</translation>
</message>
<message>
<source>Died</source>
<translation>Died</translation>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>If no server is listed above, you may also enter the server URL manually:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>Enabled</source>
<translation>Enabled</translation>
</message>
<message>
<source>Disabled</source>
<translation>Disabled</translation>
</message>
<message>
<source>Use voice remote to search</source>
<translation>Use voice remote to search</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
<translation>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Options for TV Shows.</source>
<translation>Options for TV Shows.</translation>
<extracomment>Description for TV Shows user settings.</extracomment>
</message>
<message>
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
<translation>Cinema Mode brings the cinema experience straight to your living room with the ability to play custom intros before the main feature.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Settings relating to playback and supported codec and media types.</source>
<translation>Settings relating to playback and supported codec and media types.</translation>
</message>
<message>
<source>Settings relating to how the application looks.</source>
<translation>Settings relating to how the application looks.</translation>
</message>
<message>
<source>Studios</source>
<translation>Studios</translation>
</message>
<message>
<source>Return to Top</source>
<translation>Return to Top</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Details Page</source>
<translation>Details Page</translation>
</message>
<message>
<source>Hide Taglines</source>
<translation>Hide Taglines</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Use Splashscreen as Screensaver Background</source>
<translation>Use Splashscreen as Screensaver Background</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Playback Information</source>
<translation>Playback Information</translation>
</message>
<message>
<source>Transcoding Information</source>
<translation>Transcoding Information</translation>
</message>
<message>
<source>Video Codec</source>
<translation>Video Codec</translation>
</message>
<message>
<source>Audio Codec</source>
<translation>Audio Codec</translation>
</message>
<message>
<source>direct</source>
<translation>direct</translation>
</message>
<message>
<source>Level</source>
<translation>Level</translation>
<extracomment>Video profile level</extracomment>
</message>
<message>
<source>Bit Rate</source>
<translation>Bit Rate</translation>
<extracomment>Video streaming bit rate</extracomment>
</message>
<message>
<source>Size</source>
<translation>Size</translation>
<extracomment>Video size</extracomment>
</message>
<message>
<source>WxH</source>
<translation>WxH</translation>
<extracomment>Video width x height</extracomment>
</message>
<message>
<source>Quick Connect</source>
<translation>Quick Connect</translation>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Here is your Quick Connect code:</translation>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(Dialogue will close automatically)</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Play Trailer</translation>
</message>
<message>
<source>Home Page</source>
<translation>Home Page</translation>
</message>
<message>
<source>Options for Home Page.</source>
<translation>Options for Home Page.</translation>
<extracomment>Description for Home Page user settings.</extracomment>
</message>
<message>
<source>Cinema Mode</source>
<translation>Cinema Mode</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Use Splashscreen as Home Background</source>
<translation>Use Splashscreen as Home Background</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Design Elements</source>
<translation>Design Elements</translation>
</message>
<message>
<source>Screensaver</source>
<translation>Screensaver</translation>
</message>
<message>
<source>Search now</source>
<translation>Search now</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>Go to episode</source>
<translation>Go to episode</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message>
<message>
<source>Go to season</source>
<translation>Go to season</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>You can search for Titles, People, Live TV Channels and more</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message>
<source>%1 of %2</source>
<translation>%1 of %2</translation>
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
</message>
<message>
<source>There was an error authenticating via Quick Connect.</source>
<translation>There was an error authenticating via Quick Connect.</translation>
</message>
<message>
<source>Networks</source>
<translation>Networks</translation>
</message>
<message>
<source>Shows</source>
<translation>Shows</translation>
</message>
<message>
<source>Options for Details pages.</source>
<translation>Options for Details pages.</translation>
<extracomment>Description for Details page user settings.</extracomment>
</message>
<message>
<source>Hides tagline text on details pages.</source>
<translation>Hides tagline text on details pages.</translation>
</message>
<message>
<source>Blur Unwatched Episodes</source>
<translation>Blur Unwatched Episodes</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>If enabled, images of unwatched episodes will be blurred.</source>
<translation>If enabled, images of unwatched episodes will be blurred.</translation>
</message>
<message>
<source>Options for Jellyfin&apos;s screensaver.</source>
<translation>Options for Jellyfin&apos;s screensaver.</translation>
<extracomment>Description for Screensaver user settings.</extracomment>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
</message>
<message>
<source>Options that alter the design of Jellyfin.</source>
<translation>Options that alter the design of Jellyfin.</translation>
<extracomment>Description for Design Elements user settings.</extracomment>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Hide Clock</source>
<translation>Hide Clock</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Max Days Next Up</source>
<translation>Max Days Next Up</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</source>
<translation>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Reason</source>
<translation>Reason</translation>
</message>
<message>
<source>Total Bitrate</source>
<translation>Total Bitrate</translation>
</message>
<message>
<source>Audio Channels</source>
<translation>Audio Channels</translation>
</message>
<message>
<source>Stream Information</source>
<translation>Stream Information</translation>
</message>
<message>
<source>Codec</source>
<translation>Codec</translation>
</message>
<message>
<source>Codec Tag</source>
<translation>Codec Tag</translation>
</message>
<message>
<source>Container</source>
<translation>Container</translation>
<extracomment>Video streaming container</extracomment>
</message>
<message>
<source>Video range type</source>
<translation>Video range type</translation>
</message>
<message>
<source>Pixel format</source>
<translation>Pixel format</translation>
<extracomment>Video pixel format</extracomment>
</message>
<message>
<source>Unable to find any albums or songs belonging to this artist</source>
<translation>Unable to find any albums or songs belonging to this artist</translation>
<extracomment>Popup message when we find no audio data for an artist</extracomment>
</message>
</context>
</TS>

View File

@ -489,6 +489,16 @@
<translation>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>AV1 Support</source>
<translation>AV1 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</source>
<translation>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Enabled</source>
<translation>Enabled</translation>
@ -703,11 +713,34 @@
<translation>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Next episode</source>
<translation>Next episode</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Play Trailer</translation>
</message>
<message>
<source>Direct Play H.264 Unsupported Profile Levels</source>
<translation>Direct Play H.264 Unsupported Profile Levels</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</source>
<translation>Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Direct Play HEVC Unsupported Profile Levels</source>
<translation>Direct Play HEVC Unsupported Profile Levels</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.</source>
<translation>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<source>Settings relating to playback and supported codec and media types.</source>
<translation>Settings relating to playback and supported codec and media types.</translation>
</message>
@ -822,5 +855,39 @@
<translation>Aired</translation>
<extracomment>Aired date label</extracomment>
</message>
<message>
<source>Slideshow Off</source>
<translation>Slideshow Off</translation>
</message>
<message>
<source>Slideshow On</source>
<translation>Slideshow On</translation>
</message>
<message>
<source>Slideshow Paused</source>
<translation>Slideshow Paused</translation>
</message>
<message>
<source>Slideshow Resumed</source>
<translation>Slideshow Resumed</translation>
</message>
<message>
<source>Random Off</source>
<translation>Random Off</translation>
</message>
<message>
<source>Random On</source>
<translation>Random On</translation>
</message>
<message>
<source>MPEG-4 Support</source>
<translation>MPEG-4 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</source>
<translation>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
</context>
</TS>

View File

@ -1614,5 +1614,104 @@
<source>Save Credentials?</source>
<translation>Guardar credenciales?</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Guardar Credenciales?</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error al recuperar contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Se ha encontrado un error mientras se reproducía este ítem.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error al cargar datos del canal</translation>
</message>
<message>
<source>Movies</source>
<translation>Peliculas</translation>
</message>
<message>
<source>TV Shows</source>
<translation>Programa de TV</translation>
</message>
<message>
<source>today</source>
<translation>Hoy</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>Ayer</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Error durante la reproducción</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Calificación de los Críticos</translation>
</message>
<message>
<source>Monday</source>
<translation>Lunes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Cargando información del canal</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Borrado confirmado</translation>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Ocurrió un error al recuperar los datos para este ítem desde el servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>No se pudo cargar los datos del canal desde el servidor</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene ítems</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Calificación IMDb</translation>
</message>
<message>
<source>Special Features</source>
<translation>Funciones especiales</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
</message>
<message>
<source>tomorrow</source>
<translation>Mañana</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
</context>
</TS>

View File

@ -1328,5 +1328,22 @@
<source>Save Credentials?</source>
<translation>Guardar credenciales?</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>¿Guardar credenciales?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Eliminar guardado</translation>
</message>
<message>
<source>On Now</source>
<translation>Ahora</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error al recuperar contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
</context>
</TS>

View File

@ -1884,5 +1884,320 @@
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error de Reproducción de Contenido de Canal</translation>
</message>
<message>
<source>Sign Out</source>
<translation>Terminar Sesión</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Borrar Credenciales</translation>
</message>
<message>
<source>Age</source>
<translation>Edad</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Criticas Raiting</translation>
</message>
<message>
<source>Born</source>
<translation>Nacido/a</translation>
</message>
<message>
<source>today</source>
<translation>hoy</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Reproduciendo Contenido de Canal</translation>
</message>
<message>
<source>Channels</source>
<translation>Canales</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Died</source>
<translation>Muerto/a</translation>
</message>
<message>
<source>View Channel</source>
<translation>Ver Canales</translation>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>si no hay servidores disponibles, puedes agregar manualmente la URL</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Ha ocurrido un error tratando de recuperar la información desde el servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene elementos</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>IMDb Raiting</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Fecha Agregada</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Fecha Reproducida</translation>
</message>
<message>
<source>Movies</source>
<translation>Películas</translation>
</message>
<message>
<source>yesterday</source>
<translation>ayer</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>mañana</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Martes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jueves</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Viernes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lunes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Miercoles</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sabado</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started at</source>
<translation>Comienza a</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Live</source>
<translation>En Vivo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Repetir</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guia de Television</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Record</source>
<translation>Grabar</translation>
</message>
<message>
<source>Record Series</source>
<translation>Grabar Series</translation>
</message>
<message>
<source>Close</source>
<translation>Cerrar</translation>
</message>
<message>
<source>Unknown</source>
<translation>desconocido</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Conectando con el Servidor</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>No se ha encontrado</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Save Credentials?</source>
<translation>Guardar Credenciales?</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Ha ocurrido un error al reproducir este contenido.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error Recuperando Contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Padres Raiting</translation>
</message>
<message>
<source>Change Server</source>
<translation>Cambiar de Servidor</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Clasificar</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vista</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Tiempo de Ejecución</translation>
</message>
<message>
<source>Error During Playback</source>
<translation>Error Durante la Reproducción</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>No se ha podido reproducir el Contenido del Canal de este servidor</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Fecha de Premiere</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Cuenta de Reproducción</translation>
</message>
<message>
<source>More Like This</source>
<translation>Mas de este Estilo</translation>
</message>
<message>
<source>Special Features</source>
<translation>Funciones Especiales</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
</message>
<message>
<source>TV Shows</source>
<translation>Programas de Televisión</translation>
</message>
<message>
<source>Ends at</source>
<translation>Termina a</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Cancel Recording</source>
<translation>Cancelar la grabación</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Cancelar la grabación de Series</translation>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Elige un servidor Jellyfin disponible de la red local</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Started</source>
<translation>Comenzó</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Starts at</source>
<translation>Comenzó a</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Comienza</translation>
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Terminó</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Enter the server name or IP address</source>
<translation>Agregar el nombre del servidor o direccion de IP</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Elige un servidor Jellyfin disponible de la red local:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>si no hay servidores disponibles, puedes agregar manualmente la URL:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Ha ocurrido un error tratando de recuperar la información desde el servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Reproduciendo Contenido de Canal</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene elementos</translation>
</message>
</context>
</TS>

View File

@ -2825,7 +2825,7 @@
<translation>Caractéristiques spéciales</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
<translation>Appuyez sur &apos;OK&apos; pour fermer</translation>
</message>
</message>
<message>
@ -3381,5 +3381,665 @@
<source>Delete Saved</source>
<translation>Supprimer les informations enregistrées</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Casting et équipe</translation>
</message>
<message>
<source>TV Shows</source>
<translation>Séries télé</translation>
</message>
<message>
<source>Monday</source>
<translation>Lundi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Fini à</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Note des critiques</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Nombre de lecture</translation>
</message>
<message>
<source>Born</source>
<translation>/Née</translation>
</message>
<message>
<source>Died</source>
<translation>Mort</translation>
</message>
<message>
<source>Special Features</source>
<translation>Caractéristiques spéciales</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
</message>
<message>
<source>Movies</source>
<translation>Films</translation>
</message>
<message>
<source>Tuesday</source>
<translation>Mardi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Répéter</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Classification parentale</translation>
</message>
<message>
<source>yesterday</source>
<translation>hier</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>demain</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Dimanche</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Mercredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jeudi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Vendredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Starts at</source>
<translation>Commence à</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation type="unfinished">Débute</translation>
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Commencé</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>On Now</source>
<translation>Maintenant</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Erreur lors de la récupération du contenu</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Erreur lors de la lecture</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Une erreur s&apos;est produite lors de la récupération des données de cet élément à partir du serveur.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Une erreur s&apos;est produite lors de la lecture de cet élément.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Chargement des données de la chaîne</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Erreur lors du chargement des données de la chaîne</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Impossible de charger les données de la chaîne à partir du serveur</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Ce %1 ne contient aucun élément</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nom</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Classement IMDb</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Date d&apos;ajout</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Date de lecture</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Durée</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Visualiser</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Trier</translation>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtrer</translation>
</message>
<message>
<source>Age</source>
<translation>Âge</translation>
</message>
<message>
<source>More Like This</source>
<translation>Similaires</translation>
</message>
<message>
<source>today</source>
<translation>aujourd&apos;hui</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Samedi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started at</source>
<translation>Commencé à</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Fini à</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Live</source>
<translation>En direct</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Chaînes</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Date de sortie</translation>
</message>
<message>
<source>Return to Top</source>
<translation>Retour en haut</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Voici votre code Quick Connect :</translation>
</message>
<message>
<source>Error Getting Playback Information</source>
<translation>Erreur lors de l&apos;obtention des informations de lecture</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Sélectionnez un serveur Jellyfin disponible sur votre réseau local :</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Cancel Recording</source>
<translation>Annuler l&apos;enregistrement</translation>
</message>
<message>
<source>Record</source>
<translation>Enregistrer</translation>
</message>
<message>
<source>Screensaver</source>
<translation>Écran de veille</translation>
</message>
<message>
<source>Go to episode</source>
<translation>Aller à l&apos;épisode</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message>
<message>
<source>Go to series</source>
<translation>Aller à la série</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
</message>
<message>
<source>Studios</source>
<translation>Studios</translation>
</message>
<message>
<source>Search now</source>
<translation>Rechercher maintenant</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>Vous pouvez rechercher des titres, des personnes, des chaînes de télévision en direct et plus encore</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message>
<source>Quick Connect</source>
<translation>Quick Connect</translation>
</message>
<message>
<source>%1 of %2</source>
<translation>%1 sur %2</translation>
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
</message>
<message>
<source>Options for Jellyfin&apos;s screensaver.</source>
<translation>Options de l&apos;écran de veille de Jellyfin.</translation>
<extracomment>Description for Screensaver user settings.</extracomment>
</message>
<message>
<source>Cinema Mode</source>
<translation>Mode Cinéma</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
<translation>Le Mode Cinéma vous fait vivre l&apos;expérience du cinéma directement dans votre salon en vous permettant de diffuser des intros personnalisées avant le film.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>WxH</source>
<translation>WxH</translation>
<extracomment>Video width x height</extracomment>
</message>
<message>
<source>Shows</source>
<translation>Séries</translation>
</message>
<message>
<source>Networks</source>
<translation>Réseaux</translation>
</message>
<message>
<source>User Interface</source>
<translation>Interface Utilisateur</translation>
<extracomment>Title for User Interface section in user setting screen.</extracomment>
</message>
<message>
<source>Close</source>
<translation>Fermer</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Lire la bande-annonce</translation>
</message>
<message>
<source>Item Count</source>
<translation>Nombre d&apos;éléments</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Version</source>
<translation>Version</translation>
</message>
<message>
<source>Connecting to Server</source>
<translation>Connexion au Serveur</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guide Télé</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Not found</source>
<translation>Introuvable</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>Le contenu demandé n&apos;existe pas sur le serveur</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Enter the server name or IP address</source>
<translation>Entrer le nom du serveur ou son adresse IP</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Go to season</source>
<translation>Aller à la saison</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
</message>
<message>
<source>Options for TV Shows.</source>
<translation>Options pour les Séries Télé.</translation>
<extracomment>Description for TV Shows user settings.</extracomment>
</message>
<message>
<source>Blur Unwatched Episodes</source>
<translation>Flouter les épisodes non visionnés</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>If enabled, images of unwatched episodes will be blurred.</source>
<translation>Si activé, les images des épisodes non visionnés seront floutées.</translation>
</message>
<message>
<source>Video Codec</source>
<translation>Codec Vidéo</translation>
</message>
<message>
<source>Audio Codec</source>
<translation>Codec Audio</translation>
</message>
<message>
<source>Audio Channels</source>
<translation>Canaux Audio</translation>
</message>
<message>
<source>Codec</source>
<translation>Codec</translation>
</message>
<message>
<source>Codec Tag</source>
<translation>Balise de Codec</translation>
</message>
<message>
<source>Container</source>
<translation>Conteneur</translation>
<extracomment>Video streaming container</extracomment>
</message>
<message>
<source>Size</source>
<translation>Taille</translation>
<extracomment>Video size</extracomment>
</message>
<message>
<source>Unknown</source>
<translation>Inconnu</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Enabled</source>
<translation>Activé</translation>
</message>
<message>
<source>Disabled</source>
<translation>Désactivé</translation>
</message>
<message>
<source>Playback</source>
<translation>Lecture</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>Une erreur a é rencontrée lors de la lecture de ce fichier. Le serveur n&apos;a pas communiqué les données nécessaires pour le transcodage.</translation>
<extracomment>Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url</extracomment>
</message>
<message>
<source>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</source>
<translation>Toujours afficher les titres sous les images des affiches. (Si désactivé, les titres s&apos;afficherons uniquement sous les éléments en surbrillance).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Show item count in the library and index of selected item.</source>
<translation>Afficher le nombre d&apos;éléments dans la bibliotèque et l&apos;index de l&apos;élément sélectionné.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Use voice remote to search</source>
<translation>Utiliser la télécomande vocale pour rechercher</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(La boîte de dialogue se fermera automatiquement)</translation>
</message>
<message>
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
<translation>Utiliser la touche Replay pour lentement animer la sélection du première élément du dossier. (Si désactivé, le premier élément du dossier sera immédiatement sélectionné).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Options for Details pages.</source>
<translation>Options des Pages de Détails.</translation>
<extracomment>Description for Details page user settings.</extracomment>
</message>
<message>
<source>Options for TV Shows.</source>
<translation>Options pour les Séries Télévisée.</translation>
<extracomment>Description for TV Shows user settings.</extracomment>
</message>
<message>
<source>View Channel</source>
<translation>Voir la Chaîne</translation>
</message>
<message>
<source>Use Splashscreen as Screensaver Background</source>
<translation>Utiliser le Splash Screen comme fond d&apos;écran de veille</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>WxH</source>
<translation>LxH</translation>
<extracomment>Video width x height</extracomment>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Utiliser le Splash Screen généré comme fond d&apos;écran de veille de Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</translation>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Utiliser le Splash Screen généré comme arrière plan principal de Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Hide Clock</source>
<translation>Masquer l&apos;Horloge</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Masquer toutes les horloges dans Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Settings relating to how the application looks.</source>
<translation>Réglages relatifs à l&apos;apparence de l&apos;application.</translation>
</message>
<message>
<source>Pixel format</source>
<translation>Format des pixels</translation>
<extracomment>Video pixel format</extracomment>
</message>
<message>
<source>Unable to find any albums or songs belonging to this artist</source>
<translation>Aucuns n&apos;albums ni chansons ont é trouvés pour cet artiste</translation>
<extracomment>Popup message when we find no audio data for an artist</extracomment>
</message>
<message>
<source>Starts</source>
<translation>Débute</translation>
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Record Series</source>
<translation>Enregistrer la Série</translation>
</message>
<message>
<source>Hide Taglines</source>
<translation>Masquer les étiquettes</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Media Grid options.</source>
<translation>Options de la Grille Média.</translation>
</message>
<message>
<source>Set Favorite</source>
<translation>Mettre en Favori</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
</message>
<message>
<source>Details Page</source>
<translation>Page de Détails</translation>
</message>
<message>
<source>Options for Home Page.</source>
<translation>Options pour la Page d&apos;Accueil.</translation>
<extracomment>Description for Home Page user settings.</extracomment>
</message>
<message>
<source>Max Days Next Up</source>
<translation>Nombre Max de jours dans Suivant</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</source>
<translation>Définir le nombre maximum de jours une émission doit rester dans la liste &quot;Suivant&quot; sans la regarder.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Playback Information</source>
<translation>Informations de Lecture</translation>
</message>
<message>
<source>Design Elements</source>
<translation>Élements de Désign</translation>
</message>
<message>
<source>Home Page</source>
<translation>Page d&apos;Accueil</translation>
</message>
<message>
<source>Transcoding Information</source>
<translation>Informations de Transcodage</translation>
</message>
<message>
<source>Options that alter the design of Jellyfin.</source>
<translation>Options qui modifient le design de Jellyfin.</translation>
<extracomment>Description for Design Elements user settings.</extracomment>
</message>
<message>
<source>Use Splashscreen as Home Background</source>
<translation>Utiliser le Splash Screen comme arrière-plan principal</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Media Grid</source>
<translation>Grille Média</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message>
<message>
<source>MPEG-2 Support</source>
<translation>Support MPEG-2</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Annuler l&apos;enregistrement de la Série</translation>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>Si aucun serveur n&apos;est affiché ci-dessus vous pouvez également saisir l&apos;adresse IP du serveur manuellement :</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
<translation>Prise en charge de la lecture directe du codec MPEG-2 (ex., Live TV). Cela empêchera le transcodage du contenu MPEG-2 mais utilisera sensiblement plus de bande passante.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Item Titles</source>
<translation>Titres des éléments</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Set Watched</source>
<translation>Mettre en Visionné</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>There was an error authenticating via Quick Connect.</source>
<translation>Une erreur s&apos;est produite lors de l&apos;autentification via Quick Connect.</translation>
</message>
<message>
<source>Hides tagline text on details pages.</source>
<translation>Masquer les étiquettes sur la page des détails.</translation>
</message>
<message>
<source>Settings relating to playback and supported codec and media types.</source>
<translation>Réglages relatifs à la lecture, aux codecs pris en charges et aux types de médias.</translation>
</message>
<message>
<source>Reason</source>
<translation>Raison</translation>
</message>
<message>
<source>direct</source>
<translation>direct</translation>
</message>
<message>
<source>Total Bitrate</source>
<translation>Débit Total</translation>
</message>
<message>
<source>Stream Information</source>
<translation>Informations du Flux</translation>
</message>
<message>
<source>Level</source>
<translation>Niveau</translation>
<extracomment>Video profile level</extracomment>
</message>
<message>
<source>Bit Rate</source>
<translation>Débit</translation>
<extracomment>Video streaming bit rate</extracomment>
</message>
<message>
<source>Video range type</source>
<translation>Différents types de vidéo</translation>
</message>
</context>
</TS>

View File

@ -7642,5 +7642,17 @@ elemeket</translation>
<source>Save Credentials?</source>
<translation>Mented a hitelesítő adatokat?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Mentettek Törlése</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Mented a hitelesítő adatokat?</translation>
</message>
<message>
<source>On Now</source>
<translation>Most</translation>
</message>
</context>
</TS>

View File

@ -1718,5 +1718,208 @@
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>È stato riscontrato un errore durante la riproduzione di questo oggetto.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Cast &amp; Crew</translation>
</message>
<message>
<source>TV Shows</source>
<translation>Serie TV</translation>
</message>
<message>
<source>Wednesday</source>
<translation>Mercoledi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Giovedi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started</source>
<translation>Iniziato</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Inizia</translation>
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Termina alle</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guida TV</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>In Connessione al Server</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>Il contenuto richiesto non esiste sul server</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Enter the server name or IP address</source>
<translation>Inserisci il nome o l&apos;IP del server</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Scegli un server Jellyfin dalla rete locale</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>Se il server non è nella lista, puoi anche inserire l&apos;URL:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>Error Getting Playback Information</source>
<translation>Errore nel recupero delle informazioni di riproduzione</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Martedi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>today</source>
<translation>oggi</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>Close</source>
<translation>Chiudi</translation>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Premi &quot;OK&quot; per Chiudere</translation>
</message>
<message>
<source>Movies</source>
<translation>Film</translation>
</message>
<message>
<source>yesterday</source>
<translation>ieri</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>domani</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Domenica</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lunedi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Venerdi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sabato</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started at</source>
<translation>Iniziato il</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Starts at</source>
<translation>Inizia il</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Cancel Recording</source>
<translation>Interrompi Registrazione</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Interrompi Registrazione Seria</translation>
</message>
<message>
<source>Not found</source>
<translation>Non trovato</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Terminato alle</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Live</source>
<translation>In diretta</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Replica</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canali</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>View Channel</source>
<translation>Visione del Canale</translation>
</message>
<message>
<source>Record</source>
<translation>Registra</translation>
</message>
<message>
<source>Unknown</source>
<translation>Sconosciuto</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Controllo Parentale</translation>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Scegli un server Jellyfin dalla rete locale:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Save Credentials?</source>
<translation>Salvare le credenziali?</translation>
</message>
<message>
<source>Change Server</source>
<translation>Cambia server</translation>
</message>
<message>
<source>Error During Playback</source>
<translation>Errore durante la riproduzione</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
</context>
</TS>

View File

@ -2553,5 +2553,95 @@ não contém itens</translation>
<source>Change Server</source>
<translation>Alterar servidor</translation>
</message>
<message>
<source>Change Server</source>
<translation>Mudar Servidor</translation>
</message>
<message>
<source>Sign Out</source>
<translation>Sair</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Salvar Credenciais?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Deletar Salvos</translation>
</message>
<message>
<source>On Now</source>
<translation>Passando Agora</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Erro ao Carregar Conteúdo</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Erro Durante Reprodução</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Houve um erro ao coletar dados do servidor para este item.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Carregando dados do canal</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 não possui itens</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nome</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Data de Reprodução</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Avaliação IMDb</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Data de Lançamento</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Um erro foi encontrado enquanto reproduzindo este item.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Erro ao carregar os dados do canal</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Não foi possível carregar do servidor os dados do canal</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Avaliação de críticos</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Data de Adição</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Número de Reproduções</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Classificação Etária</translation>
</message>
</context>
</TS>

View File

@ -671,5 +671,292 @@
<source>Save Credentials?</source>
<translation>Uložiť poverenia?</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Uložiť prihlasovacie údaje?</translation>
</message>
<message>
<source>Monday</source>
<translation>Pondelok</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started at</source>
<translation>Začalo o</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>View Channel</source>
<translation>Zobraziť kanál</translation>
</message>
<message>
<source>Record Series</source>
<translation>Séria nahrávok</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Zrušiť nahrávanie série</translation>
</message>
<message>
<source>Connecting to Server</source>
<translation>Pripája sa k serveru</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>Enter the server name or IP address</source>
<translation>Zadajte názov servera alebo IP adresu</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Error Getting Playback Information</source>
<translation>Chyba pri získavaní informácií o prehrávaní</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>Pri prehrávaní tejto položky sa vyskytla chyba. Server neposkytol požadované údaje na prekódovanie.</translation>
<extracomment>Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url</extracomment>
</message>
<message>
<source>Live</source>
<translation>Naživo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Piatok</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Delete Saved</source>
<translation>Odstrániť uložené</translation>
</message>
<message>
<source>On Now</source>
<translation>Teraz</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Dátum hrania</translation>
</message>
<message>
<source>TV Guide</source>
<translation>TV sprievodca</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Born</source>
<translation>Narodený</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Herci &amp; štáb</translation>
</message>
<message>
<source>More Like This</source>
<translation>Viac takých</translation>
</message>
<message>
<source>Movies</source>
<translation>Filmy</translation>
</message>
<message>
<source>Tuesday</source>
<translation>Utorok</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Starts</source>
<translation>Začína</translation>
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Skončil o</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>Požadovaný obsah na serveri neexistuje</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Meno</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Počet prehrania</translation>
</message>
<message>
<source>Died</source>
<translation>Zomrel</translation>
</message>
<message>
<source>Age</source>
<translation>Vek</translation>
</message>
<message>
<source>Special Features</source>
<translation>Špeciálne vlastnosti</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Zatvorte stlačením tlačidla OK</translation>
</message>
<message>
<source>tomorrow</source>
<translation>zajtra</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Nedeľa</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Streda</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sobota</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started</source>
<translation>Začalo</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Starts at</source>
<translation>Začne</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Končí o</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Not found</source>
<translation>Nenájdené</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Triediť</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Beh programu</translation>
</message>
<message>
<source>Version</source>
<translation>Verzia</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Tento %1 neobsahuje žiadne položky</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>IMDB hodnotenie</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Dátum pridania</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Rodičovské hodnotenie</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Dátum vydania</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vyhliadka</translation>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filter</translation>
</message>
<message>
<source>TV Shows</source>
<translation>TV relácie</translation>
</message>
<message>
<source>today</source>
<translation>dnes</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>včera</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Štvrtok</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Opakujte</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Kanály</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Record</source>
<translation>Záznam</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Zrušiť nahrávanie</translation>
</message>
<message>
<source>Close</source>
<translation>Zavrieť</translation>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Vyberte dostupný server Jellyfin z vašej lokálnej siete:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>Ak vyššie nie je uvedený žiadny server, adresu URL servera môžete zadať aj ručne:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Hodnotenie kritikov</translation>
</message>
<message>
<source>Unknown</source>
<translation>Neznámy</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Playback</source>
<translation>Prehrávanie</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
</context>
</TS>

View File

@ -1,8 +1,8 @@
## Channel Details
title=Jellyfin
major_version=1
minor_version=5
build_version=0
minor_version=6
build_version=2
### Main Menu Icons / Channel Poster Artwork
mm_icon_focus_fhd=pkg:/images/channel-poster_fhd.png

547
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "jellyfin-roku",
"version": "1.4.12",
"version": "1.6.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "jellyfin-roku",
"version": "1.4.12",
"version": "1.6.2",
"hasInstallScript": true,
"license": "GPL-2.0",
"dependencies": {
@ -18,7 +18,7 @@
},
"devDependencies": {
"@rokucommunity/bslint": "0.7.5",
"brighterscript": "0.57.2",
"brighterscript": "0.61.0",
"rooibos-cli": "1.4.0",
"ropm": "0.10.10"
}
@ -829,9 +829,9 @@
}
},
"node_modules/brighterscript": {
"version": "0.57.2",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.2.tgz",
"integrity": "sha512-idvz7lVLSN1mM/VoDt4/uJPFqdydSgCro2eIwT9vqV8z/1iNLpUtvXCWfeAbWxsbJkXWtmQq4GPkklCxc4OjrQ==",
"version": "0.61.0",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.0.tgz",
"integrity": "sha512-T7ndcFfzTBc3xq/tGgsCD91rEARMMDKsKXaw97cJ44bgEuu6Q8NaTM5KW7E81pMaCgPOMCl0k9k/npLejm4PNg==",
"dev": true,
"dependencies": {
"@rokucommunity/bslib": "^0.1.1",
@ -855,7 +855,7 @@
"p-settle": "^2.1.0",
"parse-ms": "^2.1.0",
"require-relative": "^0.8.7",
"roku-deploy": "^3.8.1",
"roku-deploy": "^3.9.2",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"vscode-languageserver": "7.0.0",
@ -1156,9 +1156,9 @@
}
},
"node_modules/brighterscript/node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
@ -2797,9 +2797,9 @@
}
},
"node_modules/decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"dev": true,
"engines": {
"node": ">=0.10"
@ -4296,9 +4296,9 @@
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -5121,9 +5121,9 @@
}
},
"node_modules/roku-deploy": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.8.1.tgz",
"integrity": "sha512-b8Q2IvBdEka4XIBNLU9CumPDto8X5zCmWQwwFfBPkJqwDnJ4U47GqKCJp1dHdmAY9H/JRYIj2TmFE4cTOUiTNw==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.2.tgz",
"integrity": "sha512-2LZyR4EhaFrka1gVmcuJO/f42tqz4clGImboVLCNem1q/PcFV5cnXNZRfqDI+MZ/n8eJto71JlZkcc7TDLt/EQ==",
"dependencies": {
"chalk": "^2.4.2",
"dateformat": "^3.0.3",
@ -5133,9 +5133,9 @@
"is-glob": "^4.0.3",
"jsonc-parser": "^2.3.0",
"jszip": "^3.6.0",
"minimatch": "^3.0.4",
"moment": "^2.29.1",
"parse-ms": "^2.1.0",
"picomatch": "^2.2.1",
"request": "^2.88.0",
"temp-dir": "^2.0.0",
"xml2js": "^0.4.23"
@ -5269,6 +5269,139 @@
"chevrotain": "7.1.1"
}
},
"node_modules/ropm/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ropm/node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/ropm/node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/ropm/node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ropm/node_modules/brighterscript": {
"version": "0.57.2",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.2.tgz",
"integrity": "sha512-idvz7lVLSN1mM/VoDt4/uJPFqdydSgCro2eIwT9vqV8z/1iNLpUtvXCWfeAbWxsbJkXWtmQq4GPkklCxc4OjrQ==",
"dev": true,
"dependencies": {
"@rokucommunity/bslib": "^0.1.1",
"@xml-tools/parser": "^1.0.7",
"array-flat-polyfill": "^1.0.1",
"chalk": "^2.4.2",
"chevrotain": "^7.0.1",
"chokidar": "^3.5.1",
"clear": "^0.1.0",
"cross-platform-clear-console": "^2.3.0",
"debounce-promise": "^3.1.0",
"eventemitter3": "^4.0.0",
"fast-glob": "^3.2.11",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"jsonc-parser": "^2.3.0",
"long": "^3.2.0",
"luxon": "^1.8.3",
"minimatch": "^3.0.4",
"moment": "^2.23.0",
"p-settle": "^2.1.0",
"parse-ms": "^2.1.0",
"require-relative": "^0.8.7",
"roku-deploy": "^3.8.1",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"vscode-languageserver": "7.0.0",
"vscode-languageserver-protocol": "3.16.0",
"vscode-languageserver-textdocument": "^1.0.1",
"vscode-uri": "^2.1.1",
"xml2js": "^0.4.19",
"yargs": "^16.2.0"
},
"bin": {
"bsc": "dist/cli.js"
}
},
"node_modules/ropm/node_modules/brighterscript/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/ropm/node_modules/brighterscript/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/ropm/node_modules/brighterscript/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/ropm/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ropm/node_modules/chevrotain": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz",
@ -5278,6 +5411,45 @@
"regexp-to-ast": "0.5.0"
}
},
"node_modules/ropm/node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/ropm/node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ropm/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@ -5293,6 +5465,60 @@
"node": ">=10"
}
},
"node_modules/ropm/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/ropm/node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ropm/node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ropm/node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ropm/node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/ropm/node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@ -5305,6 +5531,48 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/ropm/node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ropm/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/ropm/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ropm/node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/ropm/node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
@ -6966,9 +7234,9 @@
}
},
"brighterscript": {
"version": "0.57.2",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.2.tgz",
"integrity": "sha512-idvz7lVLSN1mM/VoDt4/uJPFqdydSgCro2eIwT9vqV8z/1iNLpUtvXCWfeAbWxsbJkXWtmQq4GPkklCxc4OjrQ==",
"version": "0.61.0",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.0.tgz",
"integrity": "sha512-T7ndcFfzTBc3xq/tGgsCD91rEARMMDKsKXaw97cJ44bgEuu6Q8NaTM5KW7E81pMaCgPOMCl0k9k/npLejm4PNg==",
"dev": true,
"requires": {
"@rokucommunity/bslib": "^0.1.1",
@ -6992,7 +7260,7 @@
"p-settle": "^2.1.0",
"parse-ms": "^2.1.0",
"require-relative": "^0.8.7",
"roku-deploy": "^3.8.1",
"roku-deploy": "^3.9.2",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"vscode-languageserver": "7.0.0",
@ -7013,9 +7281,9 @@
}
},
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
@ -8537,9 +8805,9 @@
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"dev": true
},
"define-property": {
@ -9715,9 +9983,9 @@
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}
@ -10335,9 +10603,9 @@
}
},
"roku-deploy": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.8.1.tgz",
"integrity": "sha512-b8Q2IvBdEka4XIBNLU9CumPDto8X5zCmWQwwFfBPkJqwDnJ4U47GqKCJp1dHdmAY9H/JRYIj2TmFE4cTOUiTNw==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.2.tgz",
"integrity": "sha512-2LZyR4EhaFrka1gVmcuJO/f42tqz4clGImboVLCNem1q/PcFV5cnXNZRfqDI+MZ/n8eJto71JlZkcc7TDLt/EQ==",
"requires": {
"chalk": "^2.4.2",
"dateformat": "^3.0.3",
@ -10347,9 +10615,9 @@
"is-glob": "^4.0.3",
"jsonc-parser": "^2.3.0",
"jszip": "^3.6.0",
"minimatch": "^3.0.4",
"moment": "^2.29.1",
"parse-ms": "^2.1.0",
"picomatch": "^2.2.1",
"request": "^2.88.0",
"temp-dir": "^2.0.0",
"xml2js": "^0.4.23"
@ -10455,6 +10723,117 @@
"chevrotain": "7.1.1"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"brighterscript": {
"version": "0.57.2",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.2.tgz",
"integrity": "sha512-idvz7lVLSN1mM/VoDt4/uJPFqdydSgCro2eIwT9vqV8z/1iNLpUtvXCWfeAbWxsbJkXWtmQq4GPkklCxc4OjrQ==",
"dev": true,
"requires": {
"@rokucommunity/bslib": "^0.1.1",
"@xml-tools/parser": "^1.0.7",
"array-flat-polyfill": "^1.0.1",
"chalk": "^2.4.2",
"chevrotain": "^7.0.1",
"chokidar": "^3.5.1",
"clear": "^0.1.0",
"cross-platform-clear-console": "^2.3.0",
"debounce-promise": "^3.1.0",
"eventemitter3": "^4.0.0",
"fast-glob": "^3.2.11",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"jsonc-parser": "^2.3.0",
"long": "^3.2.0",
"luxon": "^1.8.3",
"minimatch": "^3.0.4",
"moment": "^2.23.0",
"p-settle": "^2.1.0",
"parse-ms": "^2.1.0",
"require-relative": "^0.8.7",
"roku-deploy": "^3.8.1",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"vscode-languageserver": "7.0.0",
"vscode-languageserver-protocol": "3.16.0",
"vscode-languageserver-textdocument": "^1.0.1",
"vscode-uri": "^2.1.1",
"xml2js": "^0.4.19",
"yargs": "^16.2.0"
},
"dependencies": {
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true
}
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"chevrotain": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz",
@ -10464,6 +10843,31 @@
"regexp-to-ast": "0.5.0"
}
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@ -10476,6 +10880,45 @@
"universalify": "^2.0.0"
}
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@ -10486,6 +10929,36 @@
"universalify": "^2.0.0"
}
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "jellyfin-roku",
"version": "1.4.12",
"version": "1.6.2",
"description": "Roku app for Jellyfin media server",
"main": "index.js",
"directories": {
@ -8,7 +8,7 @@
},
"devDependencies": {
"@rokucommunity/bslint": "0.7.5",
"brighterscript": "0.57.2",
"brighterscript": "0.61.0",
"rooibos-cli": "1.4.0",
"ropm": "0.10.10"
},

View File

@ -3,6 +3,13 @@
"title": "Playback",
"description": "Settings relating to playback and supported codec and media types.",
"children": [
{
"title": "AV1 Support",
"description": "** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.",
"settingName": "playback.av1",
"type": "bool",
"default": "false"
},
{
"title": "MPEG-2 Support",
"description": "Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.",
@ -11,12 +18,26 @@
"default": "false"
},
{
"title": "Attempt Direct Play (Profile Lvl)",
"description": "Attempt Direct Play for H.264 media with unsupported profile levels (> 4.2) before falling back to transcoding if it fails.",
"title": "MPEG-4 Support",
"description": "Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.",
"settingName": "playback.mpeg4",
"type": "bool",
"default": "true"
},
{
"title": "Direct Play H.264 Unsupported Profile Levels",
"description": "Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.",
"settingName": "playback.tryDirect.h264ProfileLevel",
"type": "bool",
"default": "true"
},
{
"title": "Direct Play HEVC Unsupported Profile Levels",
"description": "Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.",
"settingName": "playback.tryDirect.hevcProfileLevel",
"type": "bool",
"default": "true"
},
{
"title": "Cinema Mode",
"description": "Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.",

View File

@ -13,13 +13,6 @@ sub Main (args as dynamic) as void
WriteAsciiFile("tmp:/scene.temp", "")
MoveFile("tmp:/scene.temp", "tmp:/scene")
' Temporary code to migrate MPEG2 setting from device setting to user setting
' Added for 1.4.13 release and should probably be removed for 1.4.15
if get_setting("playback.mpeg2") <> invalid and registry_read("playback.mpeg2", get_setting("active_user")) = invalid
set_user_setting("playback.mpeg2", get_setting("playback.mpeg2"))
end if
' End Temporary code
m.port = CreateObject("roMessagePort")
m.screen.setMessagePort(m.port)
m.scene = m.screen.CreateScene("JFScene")
@ -110,8 +103,20 @@ sub Main (args as dynamic) as void
else if isNodeEvent(msg, "selectedItem")
' If you select a library from ANYWHERE, follow this flow
selectedItem = msg.getData()
m.selectedItemType = selectedItem.type
if selectedItem.type = "CollectionFolder" or selectedItem.type = "UserView" or selectedItem.type = "Folder" or selectedItem.type = "Channel" or selectedItem.type = "Boxset"
'
if selectedItem.type = "CollectionFolder"
if selectedItem.collectionType = "movies"
group = CreateMovieLibraryView(selectedItem)
else
group = CreateItemGrid(selectedItem)
end if
sceneManager.callFunc("pushScene", group)
else if selectedItem.type = "Folder" and selectedItem.json.type = "Genre"
group = CreateMovieLibraryView(selectedItem)
sceneManager.callFunc("pushScene", group)
else if selectedItem.type = "UserView" or selectedItem.type = "Folder" or selectedItem.type = "Channel" or selectedItem.type = "Boxset"
group = CreateItemGrid(selectedItem)
sceneManager.callFunc("pushScene", group)
else if selectedItem.type = "Episode"
@ -128,6 +133,8 @@ sub Main (args as dynamic) as void
end if
else if selectedItem.type = "Series"
group = CreateSeriesDetailsGroup(selectedItem.json)
else if selectedItem.type = "Season"
group = CreateSeasonDetailsGroupByID(selectedItem.json.SeriesId, selectedItem.id)
else if selectedItem.type = "Movie"
' open movie detail page
group = CreateMovieDetailsGroup(selectedItem)
@ -332,7 +339,7 @@ sub Main (args as dynamic) as void
video_id = trailerData[0].id
video = CreateVideoPlayerGroup(video_id, mediaSourceId, audio_stream_idx)
video = CreateVideoPlayerGroup(video_id, mediaSourceId, audio_stream_idx, false, false)
if video <> invalid and video.errorMsg <> "introaborted"
sceneManager.callFunc("pushScene", video)
end if
@ -420,7 +427,7 @@ sub Main (args as dynamic) as void
if m.selectedItemType = "TvChannel" and node.state = "finished"
video = CreateVideoPlayerGroup(node.id)
m.global.sceneManager.callFunc("pushScene", video)
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.sceneManager.callFunc("deleteSceneAtIndex", 2)
else if node.state = "finished"
node.control = "stop"
@ -437,12 +444,6 @@ sub Main (args as dynamic) as void
autoPlayNextEpisode(node.id, node.showID)
end if
end if
'else if isNodeEvent(msg, "selectedExtra")
'rl = msg.getData()
'sel = rl.rowItemSelected
'? "msg.getfield():" + msg.getField()
'stop
'CreatePersonView(msg.getData())
else if type(msg) = "roDeviceInfoEvent"
event = msg.GetInfo()
group = sceneManager.callFunc("getActiveScene")

View File

@ -453,6 +453,20 @@ function CreateSeasonDetailsGroup(series, season)
return group
end function
function CreateSeasonDetailsGroupByID(seriesID, seasonID)
group = CreateObject("roSGNode", "TVEpisodes")
group.optionsAvailable = false
m.global.sceneManager.callFunc("pushScene", group)
group.seasonData = ItemMetaData(seasonID).json
group.objects = TVEpisodes(seriesID, seasonID)
group.observeField("episodeSelected", m.port)
group.observeField("quickPlayNode", m.port)
return group
end function
function CreateItemGrid(libraryItem)
group = CreateObject("roSGNode", "ItemGrid")
group.parentItem = libraryItem
@ -461,6 +475,14 @@ function CreateItemGrid(libraryItem)
return group
end function
function CreateMovieLibraryView(libraryItem)
group = CreateObject("roSGNode", "MovieLibraryView")
group.parentItem = libraryItem
group.optionsAvailable = true
group.observeField("selectedItem", m.port)
return group
end function
function CreateSearchPage()
' Search + Results Page
group = CreateObject("roSGNode", "searchResults")
@ -570,17 +592,6 @@ function CreatePersonView(personData as object) as object
return person
end function
function CreatePhotoPage(photo)
group = CreateObject("roSGNode", "PhotoDetails")
group.optionsAvailable = true
m.global.sceneManager.callFunc("pushScene", group)
group.itemContent = photo
return group
end function
sub UpdateSavedServerList()
server = get_setting("server")
username = get_setting("username")

View File

@ -45,6 +45,11 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
end if
end if
if m.videotype = "Episode" or m.videotype = "Series"
video.runTime = (meta.json.RunTimeTicks / 10000000.0)
video.content.contenttype = "episode"
end if
video.content.title = meta.title
video.showID = meta.showID
@ -213,16 +218,20 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
fully_external = false
' For h264 video, Roku spec states that it supports and Encoding level 4.1 and 4.2.
' For h264/hevc video, Roku spec states that it supports specfic encoding levels
' The device can decode content with a Higher Encoding level but may play it back with certain
' artifacts. If the user preference is set, and the only reason the server says we need to
' transcode is that the Envoding Level is not supported, then try to direct play but silently
' transcode is that the Encoding Level is not supported, then try to direct play but silently
' fall back to the transcode if that fails.
if meta.live = false and get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].TranscodingUrl <> invalid and forceTranscoding = false and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
video.directPlaySupported = true
video.transcodeAvailable = true
if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false
tryDirectPlay = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
if tryDirectPlay and m.playbackInfo.MediaSources[0].TranscodingUrl <> invalid and forceTranscoding = false
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
video.directPlaySupported = true
video.transcodeAvailable = true
end if
end if
end if
@ -420,7 +429,7 @@ sub autoPlayNextEpisode(videoID as string, showID as string)
if data <> invalid and data.Items.Count() = 2
' setup new video node
nextVideo = CreateVideoPlayerGroup(data.Items[1].Id, invalid, 1, false, false)
' remove last video scene
' remove last videoplayer scene
m.global.sceneManager.callFunc("clearPreviousScene")
if nextVideo <> invalid
m.global.sceneManager.callFunc("pushScene", nextVideo)

View File

@ -329,26 +329,32 @@ function AudioStream(id as string)
songData = AudioItem(id)
content = createObject("RoSGNode", "ContentNode")
params = {}
params.append({
"Static": "true",
"Container": songData.mediaSources[0].container
})
params.MediaSourceId = songData.mediaSources[0].id
content.url = buildURL(Substitute("Audio/{0}/stream", songData.id), params)
content.title = songData.title
content.streamformat = songData.mediaSources[0].container
playbackInfo = ItemPostPlaybackInfo(songData.id, params.MediaSourceId)
playbackInfo = ItemPostPlaybackInfo(songData.id, songData.mediaSources[0].id)
content.id = playbackInfo.PlaySessionId
if useTranscodeAudioStream(playbackInfo)
' Transcode the audio
content.url = buildURL(playbackInfo.mediaSources[0].TranscodingURL)
else
' Direct Stream the audio
params = {
"Static": "true",
"Container": songData.mediaSources[0].container,
"MediaSourceId": songData.mediaSources[0].id
}
content.streamformat = songData.mediaSources[0].container
content.url = buildURL(Substitute("Audio/{0}/stream", songData.id), params)
end if
return content
end function
function useTranscodeAudioStream(playbackInfo)
return playbackInfo.mediaSources[0] <> invalid and playbackInfo.mediaSources[0].TranscodingURL <> invalid
end function
function BackdropImage(id as string)
imgParams = { "maxHeight": "720", "maxWidth": "1280" }
return ImageURL(id, "Backdrop", imgParams)

View File

@ -18,6 +18,7 @@ end function
function getDeviceProfile() as object
playMpeg2 = get_user_setting("playback.mpeg2") = "true"
playAv1 = get_user_setting("playback.av1") = "true"
'Check if 5.1 Audio Output connected
maxAudioChannels = 2
@ -26,14 +27,19 @@ function getDeviceProfile() as object
maxAudioChannels = 6
end if
if playMpeg2 and di.CanDecodeVideo({ Codec: "mpeg2" }).Result = true
tsVideoCodecs = "h264,mpeg2video"
else
tsVideoCodecs = "h264"
addHevcProfile = false
MAIN10 = ""
tsVideoCodecs = "h264"
if di.CanDecodeVideo({ Codec: "hevc" }).Result = true
tsVideoCodecs = "h265,hevc," + tsVideoCodecs
addHevcProfile = true
if di.CanDecodeVideo({ Codec: "hevc", Profile: "main 10" }).Result
MAIN10 = "|main 10"
end if
end if
if di.CanDecodeVideo({ Codec: "hevc" }).Result = true
tsVideoCodecs = tsVideoCodecs + ",h265,hevc"
if playMpeg2 and di.CanDecodeVideo({ Codec: "mpeg2" }).Result = true
tsVideoCodecs = tsVideoCodecs + ",mpeg2video"
end if
if di.CanDecodeAudio({ Codec: "ac3" }).result
@ -42,9 +48,42 @@ function getDeviceProfile() as object
tsAudioCodecs = "aac"
end if
addAv1Profile = false
if playAv1 and di.CanDecodeVideo({ Codec: "av1" }).result
tsVideoCodecs = tsVideoCodecs + ",av1"
addAv1Profile = true
end if
addVp9Profile = false
if di.CanDecodeVideo({ Codec: "vp9" }).result
tsVideoCodecs = tsVideoCodecs + ",vp9"
addVp9Profile = true
end if
hevcVideoRangeTypes = "SDR"
vp9VideoRangeTypes = "SDR"
av1VideoRangeTypes = "SDR"
dp = di.GetDisplayProperties()
if dp.Hdr10 ' or dp.Hdr10Plus?
hevcVideoRangeTypes = hevcVideoRangeTypes + "|HDR10"
vp9VideoRangeTypes = vp9VideoRangeTypes + "|HDR10"
av1VideoRangeTypes = av1VideoRangeTypes + "|HDR10"
end if
if dp.HLG
hevcVideoRangeTypes = hevcVideoRangeTypes + "|HLG"
vp9VideoRangeTypes = vp9VideoRangeTypes + "|HLG"
av1VideoRangeTypes = av1VideoRangeTypes + "|HLG"
end if
if dp.DolbyVision
hevcVideoRangeTypes = hevcVideoRangeTypes + "|DOVI"
'vp9VideoRangeTypes = vp9VideoRangeTypes + ",DOVI" no evidence that vp9 can hold DOVI
av1VideoRangeTypes = av1VideoRangeTypes + "|DOVI"
end if
DirectPlayProfile = GetDirectPlayProfiles()
return {
deviceProfile = {
"MaxStreamingBitrate": 120000000,
"MaxStaticBitrate": 100000000,
"MusicStreamingTranscodingBitrate": 192000,
@ -133,24 +172,6 @@ function getDeviceProfile() as object
"IsRequired": false
}
]
},
{
"Type": "Video",
"Codec": "hevc",
"Conditions": [
{
"Condition": "EqualsAny",
"Property": "VideoProfile",
"Value": "main",
"IsRequired": false
},
{
"Condition": "LessThanEqual",
"Property": "VideoLevel",
"Value": StrI(120 * 5.1),
"IsRequired": false
}
]
}
],
"SubtitleProfiles": [
@ -172,12 +193,68 @@ function getDeviceProfile() as object
}
]
}
if addAv1Profile
deviceProfile.CodecProfiles.push({
"Type": "Video",
"Codec": "av1",
"Conditions": [
{
"Condition": "EqualsAny",
"Property": "VideoRangeType",
"Value": av1VideoRangeTypes,
"IsRequired": false
}
]
})
end if
if addHevcProfile
deviceProfile.CodecProfiles.push({
"Type": "Video",
"Codec": "hevc",
"Conditions": [
{
"Condition": "EqualsAny",
"Property": "VideoProfile",
"Value": "main" + MAIN10,
"IsRequired": false
},
{
"Condition": "EqualsAny",
"Property": "VideoRangeType",
"Value": hevcVideoRangeTypes,
"IsRequired": false
},
{
"Condition": "LessThanEqual",
"Property": "VideoLevel",
"Value": (120 * 5.1).ToStr(),
"IsRequired": false
}
]
})
end if
if addVp9Profile
deviceProfile.CodecProfiles.push({
"Type": "Video",
"Codec": "vp9",
"Conditions": [
{
"Condition": "EqualsAny",
"Property": "VideoRangeType",
"Value": vp9VideoRangeTypes,
"IsRequired": false
}
]
})
end if
return deviceProfile
end function
function GetDirectPlayProfiles() as object
mp4Video = "h264,mpeg4"
mp4Video = "h264"
mp4Audio = "mp3,pcm,lpcm,wav"
mkvVideo = "h264,vp8"
mkvAudio = "mp3,pcm,lpcm,wav"
@ -202,6 +279,10 @@ function GetDirectPlayProfiles() as object
mkvVideo = mkvVideo + ",mpeg2video"
end if
if get_user_setting("playback.mpeg4") = "true"
mp4Video = mp4Video + ",mpeg4"
end if
' Check for supported Audio
if di.CanDecodeAudio({ Codec: "ac3" }).result
mkvAudio = mkvAudio + ",ac3"