+
{getIconByName(expanded.current[group.id] ?
'ExpandMore' : 'ChevronRight')}
-
+
{group.title}
-
({group.channels.length})
+
({group.channels.length})
{expanded.current[group.id] && (
-
+
{group.channels.map(renderEntry)}
)}
;
diff --git a/frontend/src/component/tag-input/tag-input.tsx b/frontend/src/component/tag-input/tag-input.tsx
index f45e60600..57d0ab649 100644
--- a/frontend/src/component/tag-input/tag-input.tsx
+++ b/frontend/src/component/tag-input/tag-input.tsx
@@ -6,10 +6,11 @@ interface TagInputProps {
name: string;
values: string[];
onChange: (name: string, values: string[]) => void;
+ placeHolder?: string;
}
export default function TagInput(props: TagInputProps) {
- const { name, values, onChange } = props;
+ const { name, values, onChange, placeHolder } = props;
const handleTagsChange = (newTags: string[]) => {
onChange(name, newTags);
@@ -21,7 +22,7 @@ export default function TagInput(props: TagInputProps) {
value={values}
onChange={handleTagsChange}
name={name}
- placeHolder="Add tags..."
+ placeHolder={placeHolder ?? "Add tags..."}
/>
);
diff --git a/frontend/src/component/user-view/user-view.scss b/frontend/src/component/user-view/user-view.scss
index 282a17eb9..a19dc7ebe 100644
--- a/frontend/src/component/user-view/user-view.scss
+++ b/frontend/src/component/user-view/user-view.scss
@@ -6,19 +6,22 @@
}
.user {
- @include preferences.prefsRoot();
- @include preferences.prefsToolbar();
+ & {
+ min-height: 400px;
+ max-height: 480px;
- min-height: 400px;
- max-height: 480px;
+ @include preferences.prefsRoot();
+ @include preferences.prefsToolbar();
+ }
&__content {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- justify-content: flex-start;
- overflow: hidden;
-
+ & {
+ display: flex;
+ flex-flow: column;
+ flex: 1 1 auto;
+ justify-content: flex-start;
+ overflow: hidden;
+ }
label {
font-weight: bold;
@@ -43,31 +46,37 @@
}
&__target {
- display: flex;
- flex-flow: column;
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- overflow: hidden;
- background-color: var(--background-color);
- gap: 12px;
+ & {
+ display: flex;
+ flex-flow: column;
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ overflow: hidden;
+ background-color: var(--background-color);
+ gap: 12px;
+ }
&-target {
- font-size: 1.2rem;
- align-items: center;
- display: flex;
- flex-flow: row;
- padding: 8px;
+ & {
+ font-size: 1.2rem;
+ align-items: center;
+ display: flex;
+ flex-flow: row;
+ padding: 8px;
+ }
label {
flex: 1 1 0;
}
&-toolbar {
- flex: 0 0 auto;
+ & {
+ flex: 0 0 auto;
+ padding-right: 8px;
+ }
button {
@include common.iconButton();
}
- padding-right: 8px;
}
}
@@ -99,22 +108,26 @@
}
&-col {
- display: table-cell;
- width: auto;
- vertical-align: bottom;
- padding-left: 4px;
- padding-right: 4px;
+ & {
+ display: table-cell;
+ width: auto;
+ vertical-align: bottom;
+ padding-left: 4px;
+ padding-right: 4px;
+ }
input {
border: none !important;
height: 2rem;
}
&-toolbar {
- display: flex;
- flex-flow: row;
- align-items: flex-end;
- justify-content: flex-end;
- padding-left: 4px;
+ & {
+ display: flex;
+ flex-flow: row;
+ align-items: flex-end;
+ justify-content: flex-end;
+ padding-left: 4px;
+ }
span {
padding: 4px;
transform: scale(0.7) translateY(5px);
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 542cd1cfb..1c874a037 100755
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -14,11 +14,3 @@ root.render(
);
-
-// ReactDOM.render(
-//
-//
-//
-//
-//
-// , document.getElementById('root'));
\ No newline at end of file
diff --git a/frontend/src/model/server-config.ts b/frontend/src/model/server-config.ts
index 72cac010a..3788051fa 100644
--- a/frontend/src/model/server-config.ts
+++ b/frontend/src/model/server-config.ts
@@ -44,7 +44,14 @@ export interface TargetConfig {
kodi_style: boolean,
xtream_skip_live_direct_source: boolean,
xtream_skip_video_direct_source: boolean,
+ xtream_skip_series_direct_source: boolean,
xtream_resolve_series: boolean,
+ xtream_resolve_series_delay: number,
+ xtream_resolve_video: boolean,
+ xtream_resolve_video_delay: number,
+ m3u_include_type_in_url: boolean,
+ m3u_mask_redirect_url: boolean,
+ share_live_streams: boolean,
},
sort: {
match_as_ascii: boolean,
diff --git a/src/model/xtream.rs b/src/model/xtream.rs
index b2ce01232..d0bf4accd 100644
--- a/src/model/xtream.rs
+++ b/src/model/xtream.rs
@@ -298,7 +298,7 @@ pub struct XtreamSeriesInfoInfo {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct XtreamSeriesInfoEpisodeInfo {
- pub tmdb_id: u32,
+ pub tmdb_id: Option
,
pub releasedate: String,
pub plot: String,
pub duration_secs: u32,
diff --git a/src/processing/mod.rs b/src/processing/mod.rs
index 52cb7fd0c..a0db237a1 100644
--- a/src/processing/mod.rs
+++ b/src/processing/mod.rs
@@ -4,4 +4,6 @@ pub mod playlist_processor;
pub mod xmltv_parser;
mod playlist_watch;
mod xtream_processor;
-mod affix_processor;
\ No newline at end of file
+mod affix_processor;
+mod xtream_processor_vod;
+mod xtream_processor_series;
\ No newline at end of file
diff --git a/src/processing/playlist_processor.rs b/src/processing/playlist_processor.rs
index 299a3ce76..3736eeadf 100644
--- a/src/processing/playlist_processor.rs
+++ b/src/processing/playlist_processor.rs
@@ -26,7 +26,8 @@ use crate::model::stats::{InputStats, PlaylistStats};
use crate::processing::affix_processor::apply_affixes;
use crate::processing::playlist_watch::process_group_watch;
use crate::processing::xmltv_parser::flatten_tvguide;
-use crate::processing::xtream_processor::{playlist_resolve_series, playlist_resolve_vod};
+use crate::processing::xtream_processor_series::playlist_resolve_series;
+use crate::processing::xtream_processor_vod::playlist_resolve_vod;
use crate::repository::playlist_repository::persist_playlist;
use crate::utils::default_utils::default_as_default;
use crate::utils::download;
diff --git a/src/processing/xtream_processor.rs b/src/processing/xtream_processor.rs
index 55b492f88..e6cf9261f 100644
--- a/src/processing/xtream_processor.rs
+++ b/src/processing/xtream_processor.rs
@@ -1,63 +1,54 @@
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
-use crate::model::config::{Config, ConfigInput, ConfigTarget, InputType, TargetType};
-use crate::model::playlist::{FetchedPlaylist, PlaylistEntry, PlaylistItem, PlaylistItemType, UUIDType, XtreamCluster};
-use crate::processing::playlist_processor::ProcessingPipe;
+use crate::model::config::{Config, ConfigInput};
+use crate::model::playlist::{FetchedPlaylist, PlaylistEntry, PlaylistItem, XtreamCluster};
use crate::repository::storage::get_input_storage_path;
-use crate::repository::xtream_repository::{xtream_get_info_file_paths, xtream_update_input_vod_info_file, xtream_update_input_vod_tmdb_file};
+use crate::repository::xtream_repository::{xtream_get_info_file_paths};
use crate::repository::IndexedDocumentIndex;
use crate::utils::download;
-use serde_json::{Map, Value};
+use serde_json::Value;
use std::collections::HashSet;
-use std::fs::File;
+use std::fs::{File, OpenOptions};
use std::io::{BufWriter, Error, ErrorKind, Write};
-use std::rc::Rc;
-const TAG_VOD_INFO_INFO: &str = "info";
-const TAG_VOD_INFO_MOVIE_DATA: &str = "movie_data";
-const TAG_VOD_INFO_TMDB_ID: &str = "tmdb_id";
-const TAG_VOD_INFO_STREAM_ID: &str = "stream_id";
-pub async fn playlist_resolve_series(target: &ConfigTarget, errors: &mut Vec,
- pipe: &ProcessingPipe,
- provider_fpl: &mut FetchedPlaylist<'_>,
- processed_fpl: &mut FetchedPlaylist<'_>) {
- let (resolve_series, resolve_series_delay) =
- if let Some(options) = &target.options {
- (options.xtream_resolve_series && provider_fpl.input.input_type == InputType::Xtream && target.has_output(&TargetType::M3u),
- options.xtream_resolve_series_delay)
- } else {
- (false, 0)
- };
- if resolve_series {
- // collect all series in the processed lists
- let to_process_uuids: HashSet> = processed_fpl.playlistgroups.iter()
- .filter(|plg| plg.xtream_cluster == XtreamCluster::Series)
- .flat_map(|plg| &plg.channels)
- .filter(|pli| pli.header.borrow().item_type == PlaylistItemType::SeriesInfo)
- .map(|pli| Rc::clone(&pli.header.borrow().uuid)).collect();
- let mut series_playlist = download::get_xtream_playlist_series(provider_fpl, to_process_uuids, errors, resolve_series_delay).await;
- // original content saved into original list
- for plg in &series_playlist {
- provider_fpl.update_playlist(plg);
- }
- // run processing pipe over new items
- for f in pipe {
- let r = f(&mut series_playlist, target);
- if let Some(v) = r {
- series_playlist = v;
+const FILE_SERIES_INFO:&str = "xtream_series_info";
+const FILE_VOD_INFO:&str = "xtream_vod_info";
+const FILE_SUFFIX_WAL:&str = "wal";
+
+#[macro_export]
+macro_rules! create_resolve_options_function_for_xtream_input {
+ ($cluster:ident) => {
+ paste::paste! { // Paste Makro benötigt!
+ fn [](target: &ConfigTarget, fpl: &FetchedPlaylist) -> (bool, u16) {
+ let (resolve, resolve_delay) =
+ target.options.as_ref().map_or((false, 0), |opt| {
+ (opt.[] && fpl.input.input_type == InputType::Xtream,
+ opt.[])
+ });
+ (resolve, resolve_delay)
}
}
- // assign new items to the new playlist
- for plg in &series_playlist {
- processed_fpl.update_playlist(plg);
+ };
+}
+
+
+pub fn get_u32_from_serde_value(value: &Value) -> Option {
+ match value {
+ Value::Number(num_val) => num_val.as_u64().and_then(|val| u32::try_from(val).ok()),
+ Value::String(str_val) => {
+ match str_val.parse::() {
+ Ok(sid) => Some(sid),
+ Err(_) => None
+ }
}
+ _ => None,
}
}
-async fn playlist_resolve_vod_process_playlist_item(pli: &PlaylistItem, input: &ConfigInput, errors: &mut Vec, resolve_delay: u16) -> Option {
+pub(in crate::processing) async fn playlist_resolve_process_playlist_item(pli: &PlaylistItem, input: &ConfigInput, errors: &mut Vec, resolve_delay: u16, cluster: XtreamCluster) -> Option {
let mut result = None;
let provider_id = pli.get_provider_id().unwrap_or(0);
- if let Some(info_url) = download::get_xtream_player_api_info_url(input, XtreamCluster::Video, provider_id) {
+ if let Some(info_url) = download::get_xtream_player_api_info_url(input, cluster, provider_id) {
result = match download::get_xtream_stream_info_content(&info_url, input).await {
Ok(content) => Some(content),
Err(err) => {
@@ -72,145 +63,17 @@ async fn playlist_resolve_vod_process_playlist_item(pli: &PlaylistItem, input: &
result
}
-fn write_vod_info_content_to_temp_file(writer: &mut BufWriter<&File>, provider_id: u32, content: &str) -> std::io::Result<()> {
- let length = u32::try_from(content.len()).map_err(|err| Error::new(ErrorKind::Other, err))?;
- if length > 0 {
- writer.write_all(&provider_id.to_le_bytes())?;
- writer.write_all(&length.to_le_bytes())?;
- writer.write_all(content.as_bytes())?;
- }
- Ok(())
-}
-fn write_vod_info_tmdb_to_temp_file(writer: &mut BufWriter<&File>, provider_id: u32, tmdb_id: u32) -> std::io::Result<()> {
- writer.write_all(&provider_id.to_le_bytes())?;
- writer.write_all(&tmdb_id.to_le_bytes())?;
- Ok(())
-}
-
-fn get_resolve_video_options(target: &ConfigTarget, fpl: &FetchedPlaylist) -> (bool, u16) {
- let (resolve_movies, resolve_delay) =
- target.options.as_ref().map_or((false, 0), |opt| (opt.xtream_resolve_video && fpl.input.input_type == InputType::Xtream, opt.xtream_resolve_video_delay));
- (resolve_movies, resolve_delay)
-}
-
-fn get_u32_from_serde_value(value: &Value) -> Option {
- match value {
- Value::Number(num_val) => num_val.as_u64().and_then(|val| u32::try_from(val).ok()),
- Value::String(str_val) => {
- match str_val.parse::() {
- Ok(sid) => Some(sid),
- Err(_) => None
- }
- }
- _ => None,
- }
-}
-
-fn extract_provider_id_and_tmdb_id_from_vod_info(content: &str) -> Option<(u32, u32)> {
- if let Ok(mut doc) = serde_json::from_str::