got seek and end time working on non-direct play Enhance program duration calculation and entry creation in channel services and guide improved guide generation Add debug logging and really agressive program merging logic and placeholder avoidence in XMLTV writer Add a final pass duplicate detection and merging logic in _smartMerge function Refactor XMLTV writing logic: enhance debug logging, streamline program merging, and improve error handling backwards compatibly Human readable time Refactor program configuration modal: move optional position offsets to collapsible advanced options section Update program configuration modal: rename position offset labels to custom start and end time Changed how I build the guide based on an implementation that's closer to the original implementation and requires less changes. Reverted Unneeded Changes Simplified how StreamSeeK and custom end positions are applied in both transcoding and direct play. Implement merging of adjacent programs with the same ratingKey in TVGuideService Made merging-adjacent programs optional and disabled by default custom time can actuall be set cleanup Enhance time input validation for program duration and seek positions
957 lines
69 KiB
HTML
957 lines
69 KiB
HTML
<div>
|
|
<div class="modal" tabindex="-1" role="dialog" style="display: block; background-color: rgba(0, 0, 0, .5);">
|
|
<div ng-class="{'channel-editor-modal-big':showShuffleOptions, 'modal-dialog-scrollable': (tab !== 'programming') }" class="modal-dialog modal-xl" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<ul class="nav nav-tabs" >
|
|
<li class="nav-item" ng-repeat="x in tabOptions" >
|
|
<span class="nav-link btn btn-link {{ tab === x.id ? 'active' : ''}}" ng-click="setTab(x.id)">
|
|
<span ng-if="error.tab===x.id" class='text-danger'>
|
|
<i class='fas fa-exclamation-triangle' ></i> {{x.name}}
|
|
</span>
|
|
<span ng-if="error.tab!==x.id">
|
|
{{x.name}}
|
|
</span>
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
<h5 class="modal-title">
|
|
{{ getTitle() }}
|
|
</h5>
|
|
</div>
|
|
<!-- ============= TAB: PROPERTIES ========================= -->
|
|
<div class="modal-body" ng-if="tab == 'basic'">
|
|
|
|
<div class='form-group'>
|
|
<label class='form-label' >Channel Number:</label>
|
|
<input type="text" class='form-control' type='number' ng-model="channel.number" id='channelNumber' aria-describedby="channelNumberHelp"></input>
|
|
|
|
<small id='channelNumberHelp' class="text-danger" for='channelNumber'>{{error.number}}</small>
|
|
</div>
|
|
|
|
<div class='form-group'>
|
|
<label class='form-label' >Channel Name:</label>
|
|
<input type="text" class='form-control' ng-model="channel.name" id='channelName' aria-describedby="channelNameHelp"></input>
|
|
|
|
<small id='channelNumberHelp' class="text-danger" for='channelNumber'>{{error.name}}</small>
|
|
</div>
|
|
|
|
<div class='form-group'>
|
|
<label class='form-label' >Channel Group:</label>
|
|
<input type="text" class='form-control' ng-model="channel.groupTitle" id='groupTitle' placeholder="dizqueTV" aria-describedby="groupTitleHelp"></input>
|
|
|
|
<small id='groupTitleHelp' class="text-muted" for='channelNumber'>This is used by iptv clients to categorize the channels. You can leave it as dizqueTV if you don't need this sort of classification.</small>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="pull-right text-danger">{{error.icon}}</span>
|
|
<label for="channelIcon" class="small">Channel Icon</label>
|
|
|
|
<div class="input-group mb-1">
|
|
<input name="channelIcon" id="channelIcon" class="form-control form-control-sm" type="url" ng-model="channel.icon"></input>
|
|
<div class="input-group-append">
|
|
<input type="file"
|
|
accept="image/*"
|
|
class="form-control-file"
|
|
onchange="angular.element(this).scope().logoOnChange(event)"
|
|
name="logo"
|
|
id="logo">
|
|
</input>
|
|
</div>
|
|
</div>
|
|
|
|
<br></br>
|
|
<div>
|
|
<h6>Preview</h6>
|
|
<img ng-if="channel.icon !== ''" ng-src="{{channel.icon}}" alt="{{channel.name}}" style="max-height: 120px;"></img>
|
|
<span ng-if="channel.icon === ''">{{channel.name}}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
============= TAB: PROGRAMMING =========================
|
|
-->
|
|
<div ng-show="tab == 'programming'" class='modal-semi-body'>
|
|
<div class='form-group row' >
|
|
<label for="channelStartTime" class="small col-form-label col-md-auto">Programming Start:</label>
|
|
<div class='col-md-auto'>
|
|
<input id="channelStartTime" class="form-control form-control-sm col-md-auto" type="datetime-local" ng-model="channel.startTime" aria-describedby="startTimeHelp"></input>
|
|
<small class="text-danger" id='startTimeHelp'>{{error.startTime}}</small>
|
|
</div>
|
|
<label for="channelEndTime" class="small col-form-label col-md-auto">Programming End:</label>
|
|
<div class='col-md-auto'>
|
|
<input id="channelEndTime" class="form-control form-control-sm col-md-auto" type="datetime-local" ng-model="endTime" ng-disabled="true" aria-describedby="endTimeHelp"></input>
|
|
</div>
|
|
<div class='col-md-auto'>
|
|
<small class="text-muted form-text" id='endTimeHelp'>Programming will restart from the beginning. </small><small ng-show='channel.onDemand.isOnDemand' class="text-muted form-text" id='endTimeHelp'>For on-demand channels, the times in the schedule are tentative. </small>
|
|
</div>
|
|
|
|
</div>
|
|
<div>
|
|
<div class="flex-container">
|
|
|
|
<div class='programming-counter' style='order:4'>
|
|
<span class="small"><b>Total:</b> {{channel.programs.length}}</span>
|
|
</div>
|
|
<div class='programming-counter' ng-show='hasFlex' style='order:4'>
|
|
<span class="small"><b>Filler Lists:</b> {{channel.fillerCollections.length}}</span>
|
|
</div>
|
|
<div class='programming-counter' ng-show='hasFlex' style='order:4'>
|
|
<span class="small"><b>Fallback:</b> {{describeFallback()}}</span>
|
|
</div>
|
|
<div class='flex-pull-right' ng-style="{order: (reverseTools?3:4) }"></div>
|
|
|
|
|
|
<div class="btn-group-toggle" data-toggle="buttons" ng-show='showShuffleOptions' ng-style="{order: (reverseTools?2:4) }"
|
|
title='{{ showHelp.check ? "Hide" : "Show" }} Tool Help'
|
|
>
|
|
|
|
<label class='btn btn-sm {{showHelp.check ? "btn-primary":"btn-outline-primary"}} btn-programming-tools'>
|
|
<input type="checkbox" ng-model='showHelp.check' ><i class='fas fa-question-circle'></i></input>
|
|
</label>
|
|
</div>
|
|
|
|
<div ng-style="{order: (reverseTools?1:4) }" >
|
|
<button class="btn btn-sm btn-outline-info btn-programming-tools"
|
|
ng-click="programmingZoomIn()"
|
|
title="Higher"
|
|
>
|
|
<span class="fa fa-arrow-circle-down"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<div ng-style="{order: (reverseTools?1:4) }" >
|
|
<button class="btn btn-sm btn-outline-info btn-programming-tools"
|
|
ng-click="programmingZoomOut()"
|
|
title="Shorter"
|
|
>
|
|
<span class="fa fa-arrow-circle-up"></span>
|
|
</button>
|
|
</div>
|
|
|
|
|
|
<div ng-show='showShuffleOptions' ng-style="{order: (reverseTools?1:4) }" >
|
|
<button class="btn btn-sm btn-outline-info btn-programming-tools"
|
|
ng-click="toggleToolsDirection()"
|
|
>
|
|
<span
|
|
class="fa {{ reverseTools ? 'fa-long-arrow-alt-right' : 'fa-long-arrow-alt-left'}}"></span>
|
|
</button>
|
|
</div>
|
|
|
|
|
|
<div ng-style="{order: (reverseTools?0:4) }" >
|
|
<button class="btn btn-sm btn-secondary btn-programming-tools"
|
|
ng-click="toggleTools()"
|
|
>
|
|
<span
|
|
class="fa {{ showShuffleOptions ? 'fa-chevron-down' : 'fa-chevron-right'}}"></span> Tools
|
|
</button>
|
|
</div>
|
|
|
|
<div style='margin-left:0; order:4'>
|
|
<span class="text-danger small">{{error.programs}}</span>
|
|
<button class="btn btn-sm btn-primary" ng-click="showPlexLibrary()">
|
|
<span class="fa fa-plus"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-body programming-panes" ng-show="tab == 'programming'"
|
|
ng-style="{'max-height':programmingHeight()}"
|
|
>
|
|
<div class='row' ng-class="{'reverse': reverseTools }" >
|
|
<div vs-repeat="options" ng-class="{'programming-pane': true, 'col':true, 'd-block': showShuffleOptions, 'd-sm-none': showShuffleOptions, 'd-md-block' : showShuffleOptions, container: true, 'list-group': true, 'list-group-root': true, 'programming-programs': true}" ng-show="hasPrograms()"
|
|
dnd-drop="dropFunction(index , item)"
|
|
dnd-list=""
|
|
ng-init="setUpWatcher()"
|
|
ng-if="true"
|
|
ng-style="{'max-height':programmingHeight()}"
|
|
id="channelConfigProgramList"
|
|
>
|
|
<div ng-repeat="x in channel.programs track by x.$index"
|
|
ng-click="selectProgram(x.$index)"
|
|
class="list-group-item flex-container program-row" dnd-draggable="x" dnd-moved="" dnd-effect-allowed="move"
|
|
>
|
|
<div class="program-start">
|
|
{{ dateForGuide(x.start) }}
|
|
</div>
|
|
<div ng-style="programSquareStyle(x)"></div>
|
|
|
|
<div ng-hidden="x.isOffline" class='title' >
|
|
{{ getProgramDisplayTitle(x) }}
|
|
</div>
|
|
<div style="font-weight:ligther" ng-show="x.isOffline" class='title' >
|
|
<i ng-if="x.type !== 'redirect' " >Flex</i>
|
|
<span ng-if="x.type === 'redirect' " ><i>Redirect to channel:</i> <b>{{x.channel}}</b></span>
|
|
</div>
|
|
<div class="flex-pull-right"></div>
|
|
<button class="btn btn-sm btn-link" ng-click="removeItem(x.$index); $event.stopPropagation()">
|
|
<i class="text-danger fa fa fa-trash-alt"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div ng-class='{"col-sm-4": showShuffleOptions, "col-md-8": showShuffleOptions, "col-lg-7": showShuffleOptions, "col-xl-6": showShuffleOptions, "col" : !showShuffleOptions }' ng-if="! hasPrograms()">
|
|
<small class='text-info'>There are no programs in the channel, use the <i class='fas fa-plus'></i> button to add programs from your media library or use the Tools to add Flex time or a Channel Redirect</small>
|
|
</div>
|
|
|
|
<div ng-class='{
|
|
"col-md-4" : true,
|
|
"col-sm-12" : true,
|
|
"col-xl-6" : hasAdvancedTools(),
|
|
"col-lg-5" : hasAdvancedTools(),
|
|
"col-xl-4" : !hasAdvancedTools(),
|
|
"col-lg-3" : !hasAdvancedTools()
|
|
}' class='programming-pane tools-pane' ng-show="showShuffleOptions"
|
|
ng-style="{'max-height':programmingHeight()}"
|
|
>
|
|
|
|
<div class="row">
|
|
<div ng-class="toolWide()" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<input type="number" class="form-control form-control-sm" placeholder="Desired number of consecutive TV shows." min="1" max="10" ng-model="blockCount" style="width:5em">
|
|
</input>
|
|
</div>
|
|
<div class="input-group-prepend">
|
|
<select class="custom-select" ng-model="randomizeBlockShuffle">
|
|
<option ng-value="false" label="Fixed" />
|
|
<option ng-value="true" label="Random" />
|
|
</select>
|
|
</div>
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="blockShuffle(blockCount, randomizeBlockShuffle)">
|
|
<i class='fa fa-random' title='Block Shuffle' ></i> Block Shuffle
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Alternates TV shows in blocks of episodes. You can pick the number of episodes per show in each block and if the order of shows in each block should be randomized. Movies are moved to the bottom.</p>
|
|
|
|
</div>
|
|
|
|
<div ng-class="toolThin()" style="padding: 5px;" ng-show="hasPrograms()">
|
|
<div class="input-group">
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="randomShuffle()" aria-describedby="randomShuffleHelp" title='Random Shuffle'>
|
|
<i class='fa fa-random'></i> Random Shuffle
|
|
</button>
|
|
</div>
|
|
<p for="randomShuffleHelp" class='form-label' ng-show='showHelp.check'>
|
|
Completely randomizes the order of programs.
|
|
</p>
|
|
</div>
|
|
|
|
<div ng-class="toolThin()" style="padding: 5px;" ng-show="hasPrograms()" >
|
|
<div class="input-group">
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="cyclicShuffle()" title='Cyclic Shuffle' >
|
|
<i class='fa fa-random'></i> Cyclic Shuffle
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Like Random Shuffle, but tries to preserve the sequence of episodes for each TV show. If a TV show has multiple instances of its episodes, they are also cycled appropriately.</p>
|
|
</div>
|
|
|
|
<div ng-class="toolWide()" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<input type="number" class="form-control form-control-sm" placeholder="Repeats" min="1" max="{{maxReplicas()}}" ng-model="replicaCount" style="width:5em">
|
|
</div>
|
|
<button ng-disabled="!(replicaCount >= 2)" class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="replicate(replicaCount)" title='Replicate' >
|
|
<i class='fas fa-recycle'></i> Replicate
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Makes multiple copies of the schedule and plays them in sequence. Normally this isn't necessary, because dizqueTV will always play the schedule back from the beginning when it finishes. But creating replicas is a useful intermediary step sometimes before applying other transformations. Note that because very large channels can be problematic, the number of replicas will be limited to avoid creating really large channels.</p>
|
|
</div>
|
|
|
|
<div ng-class="toolWide()" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<input type="number" class="form-control form-control-sm" placeholder="Repeats" min="1" max="{{maxReplicas()}}" ng-model="randomReplicaCount" style="width:5em">
|
|
</div>
|
|
<button ng-disabled="!(randomReplicaCount >= 2)" class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="shuffleReplicate(randomReplicaCount)" title='Replicate & Shuffle' >
|
|
<i class='fas fa-dice'></i> Replicate & Shuffle
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Like "Replicate", it will make multiple copies of the programming. In addition it will shuffle the programs, but it will make sure not to have too small a distance between two identical programs.</p>
|
|
</div>
|
|
|
|
<div ng-class="toolThin()" style="padding: 5px;" ng-show="hasPrograms()" >
|
|
<div class='input-group'>
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="sortShows()" title='Sort TV Shows' >
|
|
<i class='fa fa-sort-alpha-down'></i> Sort TV Shows
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Sorts the list by TV Show and the episodes in each TV show by their season/episode number.
|
|
Movies are moved to the bottom of the schedule.
|
|
</p>
|
|
</div>
|
|
|
|
<div ng-class="toolThin()" style="padding: 5px;" ng-show="hasPrograms()" >
|
|
<div class="input-group">
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="sortByDate()" title='Sort Release Dates' >
|
|
<i class='fa fa-sort-numeric-down'></i> Sort Release Dates
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Sorts everything by its release date. This will only work correctly if the release dates in Plex are correct. In case any item does not have a release date specified, it will be moved to the bottom.</p>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-lg-6" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()" >
|
|
<div class="input-group">
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="equalizeShows()" title='Balance Shows' >
|
|
<i class='fa fa-balance-scale'></i> Balance Shows
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Will replicate some TV shows or delete duplicates of other TV shows in an effort to make it so the total durations of all episodes of each episode are as similar as possible. It's usually impossible to make the shows perfectly balanced without creating a really high number of duplicates, but it will try to get close. Movies are treated as a single show.</p>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-lg-6" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()" >
|
|
<div class='input-group'>
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="startFrequencyTweak()" title='Tweak Weights...'>
|
|
<i class='fa fa-balance-scale'></i> Tweak Weights...
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Similar to Balance TV Shows, but this allows you to pick the weights for each of the shows, so you can decide that some shows should be less frequent than other shows. It has similar caveats as "Balance Shows".</p>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-lg-12" style="padding: 5px;">
|
|
<div class="input-group">
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="addOffline()" title='Add Flex...'>
|
|
<i class='fa fa-plus'></i> Add Flex...
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Programs a Flex time slot. Normally you'd use pad times, restrict times or add breaks to add a large quantity of Flex times at once, but this exists for more specific cases.</p>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-lg-12" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<select class="custom-select" ng-model="nightStart"
|
|
ng-options="o.id as o.description for o in nightStartHours" ></select>
|
|
<select class="custom-select" ng-model="nightEnd"
|
|
ng-options="o.id as o.description for o in nightEndHours" ></select>
|
|
</div>
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="nightChannel(nightStart, nightEnd)" ng-disabled="nightStart==-1 || nightEnd==-1" title='Restrict Hours' >
|
|
|
|
<i class='far fa-moon'></i> Restrict Hours
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>The channel's regular programming between the specified hours. Flex time will fill up the remaining hours.</p>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-lg-12" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<select class="custom-select" ng-model="paddingOption"
|
|
ng-options="o as o.description for o in paddingOptions" ></select>
|
|
|
|
</div>
|
|
<button ng-disabled="disablePadding()" class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="padTimes(paddingOption.id, paddingOption.allow5)" title='Pad Times' >
|
|
<i class='far fa-clock'></i> Pad Times
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Adds Flex breaks after each TV episode or movie to ensure that the program starts at one of the allowed minute marks. For example, you can use this to ensure that all your programs start at either XX:00 times or XX:30 times. Removes any existing Flex periods before adding the new ones. This button might be disabled if the channel is already too large.</p>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-lg-12" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<select class="custom-select" style="width:5em" ng-model="breakAfter"
|
|
ng-options="o.id as o.description for o in breakAfterOptions" ></select>
|
|
<select class="custom-select" style="width:5em" ng-model="minBreakSize"
|
|
ng-options="o.id as o.description for o in minBreakSizeOptions" ></select>
|
|
<select class="custom-select" style="width:5em" ng-model="maxBreakSize"
|
|
ng-options="o.id as o.description for o in maxBreakSizeOptions" ></select>
|
|
</div>
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="addBreaks(breakAfter, minBreakSize, maxBreakSize)" ng-disabled="breaksDisabled()" title='Add Breaks' >
|
|
<i class='fa fa-coffee'></i> Add Breaks
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Adds Flex breaks between programs, attempting to avoid groups of consecutive programs that exceed the specified number of minutes. This button might be disabled if the channel is already too large.</p>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-lg-12" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<select class="custom-select" ng-model="rerunStart"
|
|
ng-options="o.id as o.description for o in rerunStartHours">
|
|
</select>
|
|
<select class="custom-select" ng-model="rerunBlockSize"
|
|
ng-options="o.id as o.description for o in rerunBlockSizes">
|
|
</select>
|
|
<select class="custom-select" ng-model="rerunRepeats"
|
|
ng-options="o.id as o.description for o in rerunRepeatOptions">
|
|
</select>
|
|
</div>
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="doReruns(rerunStart, rerunBlockSize, rerunRepeats)" ng-disabled="rerunsDisabled()" title='Reruns' >
|
|
<i class='far fa-clone'></i> Reruns
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Divides the programming in blocks of 6, 8 or 12 hours then repeats each of the blocks the specified number of times. For example, you can make a channel that plays exactly the same channels in the morning and in the afternoon. This button might be disabled if the channel is already too large.</p>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-lg-12" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group" >
|
|
<div class="input-group-prepend">
|
|
<button class="btn btn-sm btn-secondary form-control form-control-sm" type="button" ng-click="savePositions()">
|
|
<i class='fa fa-file-import'></i> Save
|
|
</button>
|
|
</div>
|
|
|
|
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="recoverPositions()" ng-disabled='cannotRecoverPositions()' title='Recover Episode Positions' >
|
|
<i class='fa fa-file-export'></i> Recover Episode Positions
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>The "Save" button saves the current episodes that are next to be played for each tv show. Then whenever you click the "Recover Episode Popsitions" button, episodes will be rearranged cyclically and they will start with the saved positions. So you can maintain episode sequences even after modifying the channel. If there are any new TV shows, they will start at their current positions. Movies and specials won't change positions.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-lg-12" style="padding: 5px;" >
|
|
<div class='input-group'>
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="addRedirect()" title='Add Redirect...' >
|
|
<i class='fas fa-external-link-alt'></i> Add Redirect...
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Adds a channel redirect. During this period of time, the channel will redirect to another channel.</p>
|
|
</div>
|
|
|
|
<div class="col-xl-6 col-lg-12" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<div class='loader' ng-hide='channelsDownloaded'></div>
|
|
<select class="custom-select" ng-show='channelsDownloaded' style='width:5em;' ng-model="atNightChannelNumber"
|
|
ng-options="o.id as o.description for o in knownChannels" ></select>
|
|
<select class="custom-select" ng-model="atNightStart"
|
|
ng-options="o.id as o.description for o in nightStartHours" ></select>
|
|
<select class="custom-select" ng-model="atNightEnd"
|
|
ng-options="o.id as o.description for o in nightEndHours" ></select>
|
|
</div>
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="nightChannel(atNightEnd, atNightStart, atNightChannelNumber)" ng-disabled="atNightChannelNumber==-1 || atNightStart==-1 || atNightEnd==-1" title='"Channel at Night"' >
|
|
<i class='far fa-moon'></i> "Channel at Night"
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Will redirect to another channel while between the selected hours.</p>
|
|
</div>
|
|
|
|
<div ng-class="toolWide()" style="padding: 5px;" ng-show="hasPrograms()">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<button class='btn btn-sm btn-warning form-control' ng-click="slideAllPrograms(-slide.value)"
|
|
ng-disabled="slide.value == -1"
|
|
title="Rewind"
|
|
>
|
|
<i class='fas fa-backward'></i> Rewind
|
|
</button>
|
|
</div>
|
|
<select class="custom-select" ng-model="slide.value"
|
|
ng-options="o.id as o.description for o in slide.options" ></select>
|
|
<div class="input-group-append">
|
|
<button class='btn btn-sm btn-warning form-control' ng-click="slideAllPrograms(slide.value)"
|
|
ng-disabled="slide.value == -1"
|
|
title="Fast-Forward"
|
|
>
|
|
<i class='fas fa-forward'></i> Fast-Forward
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Slides the whole schedule. The "Fast-Forward" button will advance the stream by the specified amount of time. The "Rewind" button does the opposite.</p>
|
|
</div>
|
|
|
|
<div ng-class="toolThin()" style="padding: 5px;" ng-show="hasPrograms()">
|
|
<div class="input-group" >
|
|
<div class="input-group-prepend">
|
|
<button class="btn btn-sm btn-warning form-control"
|
|
type="button"
|
|
ng-click="rerollTimeSlots()"
|
|
ng-disabled="hasNoTimeSlots()"
|
|
title = "Regenerate time slots..."
|
|
>
|
|
<i class='fas fa-redo'></i>
|
|
</button>
|
|
</div>
|
|
|
|
<button class='btn btn-sm btn-warning form-control' ng-click="onTimeSlotsButtonClick()"
|
|
title="Time Slots..."
|
|
>
|
|
<i class='fas fa-blender'></i> Time Slots...
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>This allows to schedule specific shows to run at specific time slots of the day or a week. It's recommended you first populate the channel with the episodes from the shows you want to play and/or other content like movies and redirects.</p>
|
|
|
|
</div>
|
|
|
|
<div ng-class="toolThin()" style="padding: 5px;" ng-show="hasPrograms()">
|
|
|
|
|
|
<div class="input-group" >
|
|
<div class="input-group-prepend">
|
|
<button class="btn btn-sm btn-warning form-control"
|
|
type="button"
|
|
ng-click="rerollRandomSlots()"
|
|
ng-disabled="hasNoRandomSlots()"
|
|
title = "Regenerate random slots..."
|
|
>
|
|
<i class='fas fa-redo'></i>
|
|
</button>
|
|
</div>
|
|
|
|
<button class='btn btn-sm btn-warning form-control' ng-click="onRandomSlotsButtonClick()"
|
|
title="Random Slots..."
|
|
>
|
|
<i class='fas fa-flask'></i> Random Slots...
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>This is similar to Time Slots, but instead of time sections, you pick a probability to play each tv show and the length of the block. Once a channel has been configured with random slots, the reload button can re-evaluate them again, with the saved settings.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-md-auto" style="padding: 5px;" ng-show="hasPrograms()">
|
|
<div class='input-group'>
|
|
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="removeDuplicates()" title='Remove Duplicates' >
|
|
<i class='fa fa-trash-alt'></i> Duplicates
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Removes repeated videos.</p>
|
|
</div>
|
|
|
|
<div class="col-md-auto" style="padding: 5px;" ng-show="hasPrograms()">
|
|
<div class='input-group'>
|
|
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="removeOffline()" title='Remove Flex' >
|
|
<i class='fa fa-trash-alt'></i> Flex
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Removes any Flex periods from the schedule.</p>
|
|
</div>
|
|
|
|
<div class="col-md-auto" style="padding: 5px;" ng-show="hasPrograms() && hasAdvancedTools()">
|
|
<div class='input-group'>
|
|
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="wipeSpecials()" title='Remove Specials' >
|
|
<i class='fa fa-trash-alt'></i> Specials
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Removes any specials from the schedule. Specials are episodes with season "00".</p>
|
|
</div>
|
|
|
|
<div class="col-md-auto" style="padding: 5px;" ng-show="hasPrograms()">
|
|
<div class='input-group'>
|
|
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="startRemoveShows()" title='Remove Show(s)...' >
|
|
<i class='fa fa-trash-alt'></i> Show(s)...
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Allows you to pick specific shows to remove from the channel.</p>
|
|
</div>
|
|
|
|
<div class="col" style="padding: 5px;" ng-show="hasPrograms()">
|
|
<div class='input-group'>
|
|
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="wipeSchedule()" title='Remove All' >
|
|
<i class='fa fa-trash-alt'></i> All
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Wipes out the schedule so that you can start over.</p>
|
|
</div>
|
|
|
|
|
|
<br />
|
|
<div class="col-xl-6 col-lg-6" style="padding: 5px;" ng-show="hasPrograms()">
|
|
|
|
|
|
<div class="input-group" >
|
|
|
|
<button class='btn btn-sm btn-outline-secondary form-control' ng-click="toggleAdvanced()"
|
|
title="Toggle extra tools..."
|
|
>
|
|
<i class='fas fa-tools'></i> {{ hasAdvancedTools() ? "Less" : "More"}} Tools...
|
|
</button>
|
|
</div>
|
|
<p ng-show='showHelp.check'>Use this button to show or hide a bunch of additional tools that might be useful.</p>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
============= TAB: FLEX =========================
|
|
-->
|
|
|
|
<div class="modal-body" ng-if="tab == 'flex'">
|
|
<div class='text-center text-info' ng-show='!hasFlex'>
|
|
<i class='fas fa-info-circle' ></i> Use programming tools like "Add Padding", "Add Breaks" or "Add Flex" to add "Flex" time slots to the channel's programming.
|
|
</div>
|
|
<div class='row'>
|
|
<div class="col-md-12">
|
|
<label for="offlineMode" class="small">Fallback Mode:</label>
|
|
<div class="input-group mb-1">
|
|
<select class="form-control form-control-sm" id="offlineMode" ng-model="channel.offlineMode">
|
|
<option value="pic">Picture</option>
|
|
<option value="clip">Clip from Library</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div ng-show="channel.offlineMode == 'clip'" >
|
|
<p class="text-center text-info">Pick a video clip that will be used for fallback when there's no appropriate
|
|
filler available for the time duration. It's recommended to use countdown or looping videos for this. <span class="text-danger">{{error.fallback}}</span></p>
|
|
</div>
|
|
|
|
<div class='row' ng-show="channel.offlineMode == 'pic'" >
|
|
<div class="col-md-3">
|
|
<img ng-src="{{channel.offlinePicture}}" alt="Fallback preview" style="max-height: 120px;"></img>
|
|
</div>
|
|
<div class="col-md-9">
|
|
<div>
|
|
<label for="offlinePicture" class="small">
|
|
Picture: <span class="text-danger pull-right">{{error.picture}}</span></label>
|
|
<input name="offlinePicture" id="offlinePicture" class="form-control form-control-sm" type="url" ng-model="channel.offlinePicture"></input>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="offlineSound" class="small">Sound Track:<span class="text-danger pull-right">{{error.sound}}</span></label>
|
|
<input name="offlineSound" id="offlineSound" class="form-control form-control-sm" type="url" ng-model="channel.offlineSoundtrack" placeholder="URL to a sound track that will loop during the offline screen, leave empty for silence."></input>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class='row' ng-show="channel.offlineMode == 'pic'" >
|
|
<div class='col-md-12'><p class="text-center text-info">This picture is used in case there are no filler clips available with a shorter length than the Flex time duration. Requires ffmpeg transcoding.</p></div>
|
|
</div>
|
|
|
|
<div ng-show="channel.offlineMode == 'clip'">
|
|
<div class="list-group list-group-root" dnd-list="channel.fallback">
|
|
<div class="list-group-item flex-container" style="cursor: default;" ng-repeat="x in channel.fallback" dnd-draggable="x" dnd-moved="channel.fallback.splice($index, 1)" dnd-effect-allowed="move">
|
|
<div class="program-start" >
|
|
{{durationString(x.duration)}}
|
|
</div>
|
|
<div ng-style="programSquareStyle(x, true)"></div>
|
|
<div style="margin-right: 5px;">
|
|
<strong>Fallback:</strong> {{x.title}}
|
|
</div>
|
|
<div class="flex-pull-right">
|
|
<button class="btn btn-sm btn-link" ng-click="channel.fallback.splice($index,1)">
|
|
<i class="text-danger fa fa-trash-alt" ></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div ng-show="channel.fallback.length === 0">
|
|
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="openFallbackLibrary()">Pick fallback</button>
|
|
</div>
|
|
<hr style='margin-top:0'></hr>
|
|
</div>
|
|
|
|
<div>
|
|
<h5 style="margin-top: 10px;">Filler</h5>
|
|
<div>
|
|
<label>Minimum time before replaying a filler (Minutes): </label>
|
|
<input type="number" class="form-control form-control-sm" ng-model="channel.fillerRepeatCooldownMinutes" ng-pattern="/^([1-9][0-9]*)$/" min='0' max='10080'></input>
|
|
|
|
<span class="text-danger pull-right">{{error.blockRepeats}}</span>
|
|
</div>
|
|
<div>
|
|
<input id="overlayDiableIcon" type="checkbox" ng-model="channel.disableFillerOverlay">
|
|
<label class="small" for="overlayDisableIcon" style="margin-bottom: 4px;"> Disable channel watermark when playing filler </label>
|
|
</div>
|
|
<hr></hr>
|
|
<h6>Filler Lists</h6>
|
|
<div id='fillerContainer'>
|
|
<br></br>
|
|
|
|
<div class="form-row" ng-repeat = "x in channel.fillerCollections" track-by = "$index">
|
|
<div class='form-group col-md-5'>
|
|
<label ng-if="$index==0" for="fillerselect{{$index}}">List</label>
|
|
<select
|
|
id="fillerselect{{$index}}" class="custom-select form-control"
|
|
ng-model="x.id" ng-options="o.id as o.name for o in x.options"
|
|
ng-change="refreshFillerStuff()"
|
|
>
|
|
</select>
|
|
</div>
|
|
<div class='form-group col-md-2' ng-if="x.id !== 'none' " >
|
|
<label ng-if="$index==0" for="cooldown{{$index}}">Cooldown (minutes)</label>
|
|
<input class='form-control' id="cooldown{{$index}}" type='number' ng-model='x.cooldownMinutes' ng-pattern="/^([0-9][0-9]*)$/"
|
|
min='0' max='10080'
|
|
data-toggle="tooltip" data-placement="bottom" title="The channel won't pick a video from this list if it played something from this list less than this amount of minutes ago."
|
|
> </input>
|
|
</div>
|
|
<div class='form-group col-md-2' ng-if="x.id === 'none' " >
|
|
</div>
|
|
<div class='form-group col-md-3' ng-if="x.id !== 'none' && channel.fillerCollections.length > 2 " >
|
|
<label ng-if="$index==0" for="fillerrange{{$index}}">Weight</label>
|
|
<input class='form-control-range custom-range' id="fillerrange{{$index}}" type='range' ng-model='x.weight' min=1 max=600
|
|
data-toggle="tooltip" data-placement="bottom" title="Lists with more weight will be picked more frequently."
|
|
ng-change="refreshFillerStuff()"
|
|
>
|
|
</input>
|
|
</div>
|
|
<div class='form-group col-md-4' ng-if="x.id === 'none' || channel.fillerCollections.length <= 2 " >
|
|
</div>
|
|
<div class='form-group col-md-1' ng-if="x.id !== 'none' && channel.fillerCollections.length > 2" >
|
|
<label ng-if="$index==0" for="fillerp{{$index}}">%</label>
|
|
<input class='form-control flex-filler-percent' id="fillerp{{$index}}" type='text' ng-model='x.percentage'
|
|
data-toggle="tooltip" data-placement="bottom" title="This is the overall probability this list might be picked, assuming all lists are available." readonly
|
|
>
|
|
</input>
|
|
</div>
|
|
<div class='form-group col-md-1' ng-if="x.id !== 'none'" >
|
|
<label ng-if="$index==0" for="delete{{$index}}">-</label>
|
|
<button id='delete{{$index}}' class='btn btn-link form-control' ng-click='deleteFillerList($index)' >
|
|
<i class='text-danger fa fa-trash-alt'></i>
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
<hr></hr >
|
|
<p class="text-center text-info">Videos from the filler list will be randomly picked to play unless there are cooldown restrictions to place or if no videos are short enough for the remaining Flex time. Use the Filler tab at the main page to create Filler Lists. If no videos are available, the fallback will be used instead.</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!--
|
|
============= TAB: EPG =========================
|
|
-->
|
|
|
|
<div class="modal-body" ng-if="tab == 'epg'">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" id="stealth" aria-describedby="stealthHelp" ng-model='channel.stealth'>
|
|
<label class="form-check-label" for="stealth">Stealth Mode</label>
|
|
|
|
<span class='text-muted' id="stealthHelp">(This will hide the channel from TV guides, spoofed HDHR, m3u playlist... The channel can still be streamed directly or be used as a redirect target.)</span>
|
|
</div>
|
|
|
|
<div class='form-group' ng-show='! channel.stealth'>
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" id="mergeAdjacentPrograms" ng-model="channel.mergeAdjacentPrograms">
|
|
<label class="form-check-label" for="mergeAdjacentPrograms">Merge adjacent programs with same content</label>
|
|
|
|
<span class='text-muted' id="mergeAdjacentProgramsHelp">(When enabled, adjacent programs with the same content ID will appear as a single program in the guide. This is useful for shows split by commercials or bumpers.)</span>
|
|
</div>
|
|
</div>
|
|
<br></br>
|
|
<div class='form-group' ng-show='! channel.stealth'>
|
|
<label class='form-label' >Placeholder program title:</label>
|
|
<input type="text" class='form-control' ng-model="channel.guideFlexPlaceholder" placeholder="Leave empty so that it uses the channel's name" id='guideFlex' aria-describedby="guideFlexHelp"></input>
|
|
|
|
<small id='guideFlexHelp' class="text-muted" for='guideFlex'>This is the name of the fake program that will appear in the TV guide when there are no programs to display in that time slot guide. E.g when a large Flex block is scheduled.</small>
|
|
</div>
|
|
|
|
<div class='form-group' ng-show='! channel.stealth'>
|
|
<label class='form-label'>Minimum program duration to appear in the TV guide (seconds): </label>
|
|
<input type="number" class="form-control" ng-model="channel.guideMinimumDurationSeconds" ng-pattern="/^([0-9][0-9]*)$/" min='0' max='36288000' id='guideFlexTime' aria-describedby="guideFlexTimeHelp"></input>
|
|
<small id='guideFlexTimeHelp' class="text-muted" for='guideFlexTime'>Programs shorter than this value will be treated the same as Flex time. Meaning that the TV Guide will try to meld them with the previous program or display the block of programs as the "place holder program" if they make a large continuous group. Use 0 to disable this feature or use a large value to make the channel report only the placeholder program and not the real programming.</small>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
============= TAB: ffmpeg =========================
|
|
-->
|
|
|
|
<div class="modal-body" ng-if="tab == 'ffmpeg'">
|
|
<small class='text-info'>These features require ffmpeg transcoding to be enabled in FFmpeg settings</small>
|
|
<hr></hr>
|
|
<h6>Channel Watermark</h6>
|
|
|
|
<div class='form-check'>
|
|
<input class="form-check-input" type="checkbox" ng-model="channel.watermark.enabled" id="overlayCheck"></input>
|
|
<label class="form-check-label" for="overlayCheck">
|
|
Enable Watermark
|
|
</label>
|
|
<small class='text-muted form-ext' >Renders a channel icon (also known as bug or Digital On-screen Graphic) on top of the channel's stream.</small>
|
|
</div>
|
|
|
|
<div ng-show="channel.watermark.enabled" class='row' >
|
|
|
|
<div class='col-md-3 col-lg-4 col-xl-5'>
|
|
<h7>Preview</h7>
|
|
<div ng-style='getWatermarkPreviewOuter()' class='watermark-preview'>
|
|
<div ng-style='getWatermarkPreviewRectangle(4,3)' class='alternate-aspect' ></div>
|
|
<div ng-style='getWatermarkPreviewRectangle(16,9)' class='alternate-aspect' ></div>
|
|
<img src='{{ getWatermarkSrc() }}' ng-style='getWatermarkPreviewInner()'></img>
|
|
</div>
|
|
</div>
|
|
|
|
<div class='col'>
|
|
<small class='text-danger' ng-show='error.watermark'>{{ error.watermark }}</small>
|
|
|
|
<div class='form-group'>
|
|
<label for="overlayURL">
|
|
Watermark Picture URL:
|
|
</label>
|
|
|
|
<input id='overlayURL' class='form-control' type='url' ng-model='channel.watermark.url' placeholder="Leave empty to use the channel's icon.">
|
|
</input>
|
|
<div class="input-group-append">
|
|
<input type="file"
|
|
accept="image/*"
|
|
class="form-control-file"
|
|
onchange="angular.element(this).scope().watermarkOnChange(event)"
|
|
name="logo"
|
|
id="logo">
|
|
</input>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="watermarkPosition">Position:</label>
|
|
<select class="form-control custom-select" id="watermarkPosition" ng-model="channel.watermark.position">
|
|
<option value="top-left">Top Left</option>
|
|
<option value="top-right">Top Right</option>
|
|
<option value="bottom-left">Bottom Left</option>
|
|
<option value="bottom-right">Bottom Right</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class='form-row'>
|
|
<div class='form-group col-sm-auto'>
|
|
<label for="watermarkWidth" >
|
|
Width %:
|
|
</label>
|
|
<input id='watermarkWidth' class='form-control' type='number' ng-model='channel.watermark.width' min="0" max="100" step="any" ng-disabled='channel.watermark.fixedSize' >
|
|
</input>
|
|
</div>
|
|
<div class='form-group col-sm-auto'>
|
|
<label for="watermarkHorizontal">
|
|
Horizontal Margin %:
|
|
</label>
|
|
<input id='watermarkHorizontal' class='form-control' type='number' ng-model='channel.watermark.horizontalMargin' min="0" max="100" step="any">
|
|
</input>
|
|
</div>
|
|
<div class='form-group col-sm-auto'>
|
|
<label for="watermarkVertical">
|
|
Vertical Margin %:
|
|
</label>
|
|
<input id='watermarkVertical' class='form-control' type='number' ng-model='channel.watermark.verticalMargin' min="0" max="100" step="any" >
|
|
</input>
|
|
</div>
|
|
</div>
|
|
<br>
|
|
<div class='form-check col-sm-auto'>
|
|
<input class="form-check-input" type="checkbox" ng-model="channel.watermark.fixedSize" id="overlayFixed"></input>
|
|
<label class="form-check-label" for="overlayFixed">
|
|
Disable Image Scaling
|
|
</label>
|
|
<small class='text-muted form-text' >The image will be rendered at its actual size without applying any scaling to it.</small>
|
|
</div>
|
|
<div class='form-check col-sm-auto'>
|
|
<input class="form-check-input" type="checkbox" ng-model="channel.watermark.animated" id="overlayAnimated"></input>
|
|
<label class="form-check-label" for="overlayAnimated">
|
|
Animated Image
|
|
</label>
|
|
<small class='text-muted form-text' >Tick this if and only if the watermark is an animated GIF or PNG. It will make it loop or not loop according to the image's configuration. If the image is not animated, there will be playback errors.</small>
|
|
</div>
|
|
|
|
<br>
|
|
|
|
<div class='form-group'>
|
|
<label for="overlayDuration">
|
|
Overlay Duration (seconds) (0 = permanent):
|
|
</label>
|
|
<input id='overlayDuration' class='form-control' type='number' ng-model='channel.watermark.duration' min=0>
|
|
</input>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<hr></hr>
|
|
<h6>Transcoding settings</h6>
|
|
|
|
|
|
<div class='row'>
|
|
<div class="form-group col-sm-auto">
|
|
<label for="channelResolution">Channel Resolution:</label>
|
|
<select class="form-control custom-select" id="channelResolution" ng-model="channel.transcoding.targetResolution"
|
|
ng-options="o.id as o.description for o in resolutionOptions"
|
|
>
|
|
</select>
|
|
</div>
|
|
|
|
|
|
<div class="form-group col-sm-auto">
|
|
<label for="channelBitrate">Video Bitrate (K):</label>
|
|
<input id='channelBitrate' class='form-control' type='number' ng-model='channel.transcoding.videoBitrate' min=0 placeholder='{{videoRateDefault}}'>
|
|
</input>
|
|
<small class='text-muted form-text'>Leave unassigned to use the global setting</small>
|
|
</div>
|
|
|
|
<div class="form-group col-sm-auto">
|
|
<label for="channelBufsize">Video Buffer Size (K):</label>
|
|
<input id='channelBufsize' class='form-control' type='number' ng-model='channel.transcoding.videoBufSize' min=0 placeholder='{{videoBufSizeDefault}}'>
|
|
</input>
|
|
<small class='text-muted form-text'>Leave unassigned to use the global setting</small>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!--
|
|
============= TAB: ON-DEMAND =========================
|
|
-->
|
|
|
|
<div class="modal-body" ng-if="tab == 'ondemand'">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" id="onDemand" aria-describedby="onDemandHelp" ng-model='channel.onDemand.isOnDemand'>
|
|
<label class="form-check-label" for="onDemand">On-Demand</label>
|
|
|
|
<span class='text-muted' id="stealthHelp">(The channel's programming will be paused when it is not being played. No programs will appear in the TV-guide while the channel is paused.)</span>
|
|
</div>
|
|
<br></br>
|
|
|
|
<div class='form-group' ng-show='channel.onDemand.isOnDemand'>
|
|
<label class='form-label' for="segmentLength" >Segment Length:</label>
|
|
|
|
<select class="form-control custom-select" id="segmentLength" ng-model="channel.onDemand.modulo" convert-to-number >
|
|
<option ng-value="1">Instant</option>
|
|
<option ng-value="300000">5 minutes</option>
|
|
<option ng-value="600000">10 minutes</option>
|
|
<option ng-value="900000">15 minutes</option>
|
|
<option ng-value="1800000">30 minutes</option>
|
|
<option ng-value="6000000">1 hour</option>
|
|
</select>
|
|
|
|
|
|
<small id='guideFlexHelp' class="text-muted" for='guideFlex'>Channel will be divided in segments. For example, if you use padding or time slots in your channel so that everything starts at 0:00 or 0:30 , you want a 30 minutes-segment. Use no segment if you want the channel to play exactly where you left it. Flex time will be added if necessary for padding.</small>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-footer">
|
|
<span class="pull-right text-danger" ng-show="error.any"> <i class='fa fa-exclamation-triangle'></i> There were errors. Please review the form.</span>
|
|
<span class="pull-right text-info" ng-show='! hasPrograms() && (tab != "programming")'> <i class='fas fa-info-circle'></i> Use the "Programming" tab to add programs to the channel.</span>
|
|
<div class="text-right">
|
|
<button class="btn btn-sm btn-link" ng-click="_onDone()">
|
|
Cancel
|
|
</button>
|
|
<button class="btn btn-sm btn-primary" ng-click="_onDone(channel)" ng-disabled='! hasPrograms()'>
|
|
{{ isNewChannel ? 'Add Channel' : 'Update Channel' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<program-config program="_selectedProgram" on-done="finshedProgramEdit"></program-config>
|
|
<flex-config offline-title="Modify Flex Time" program="_selectedOffline" on-done="finishedOfflineEdit"></flex-config>
|
|
<frequency-tweak programs="_programFrequencies" message="_frequencyMessage" modified="_frequencyModified" on-done="tweakFrequencies"></frequency-tweak>
|
|
<remove-shows program-infos="_removablePrograms" on-done="removeShows" deleted="_deletedProgramNames"></remove-shows>
|
|
<flex-config offline-title="Add Flex Time" program="_addingOffline" on-done="finishedAddingOffline"></flex-config>
|
|
<plex-library limit="libraryLimit" height="300" visible="displayPlexLibrary" position-choice=true on-finish="importPrograms"></plex-library>
|
|
<plex-library height="300" limit=1 visible="showFallbackPlexLibrary" on-finish="importFallback"></plex-library>
|
|
<channel-redirect visible="_displayRedirect" on-done="finishRedirect" form-title="_redirectTitle" program="_selectedRedirect" ></channel-redirect>
|
|
<time-slots-schedule-editor linker="registerTimeSlots" on-done="onTimeSlotsDone"></time-slots-schedule-editor>
|
|
<random-slots-schedule-editor linker="registerRandomSlots" on-done="onRandomSlotsDone"></random-slots-schedule-editor>
|
|
</div>
|