@christianriedl/media 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,149 @@
1
+ <script setup lang="ts">
2
+ import { reactive, inject, ref, computed, onMounted, onUnmounted } from 'vue';
3
+ import { useRouter } from 'vue-router';
4
+ import { Helper, IValueText, IAppState } from '@christianriedl/utils';
5
+ import { IMediaInstanceService } from '@christianriedl/media';
6
+ import RootInformationLine from '../components/RootInformationLine.vue'
7
+ import RootStatisticsLine from '../components/RootStatisticsLine.vue'
8
+
9
+ const appState = inject<IAppState>('appstate');
10
+ const getMediaInstanceService = inject<() => IMediaInstanceService>('get-mediainstance');
11
+ const instanceService = getMediaInstanceService!();
12
+ const info = instanceService.instanceInformation;
13
+ const stat = instanceService.scanStatistics;
14
+ const heightStyle = computed(() => { return { height: appState!.bodyHeight.value + 'px' } });
15
+
16
+ const listtime = computed(() => unixTimeToString(info.listModificationTime));
17
+ const roottime = computed(() => unixTimeToString(info.rootModificationTime));
18
+ const success = ref(false);
19
+ const text = ref('');
20
+ let timer = -1;
21
+
22
+ instanceService.getInformation()
23
+ .then((info) => { });
24
+
25
+ function unixTimeToString(unixtime: number, withSeconds?: boolean) {
26
+ const dt = Helper.fromUnixTime(unixtime);
27
+ return Helper.formatDate(dt) + ' ' + Helper.formatTime(dt, withSeconds);
28
+ }
29
+ function getStatistics() {
30
+ timer = -1;
31
+ instanceService.getScanState()
32
+ .then((stat) => timer = window.setTimeout(getStatistics, 500));
33
+ }
34
+ async function onCreate() {
35
+ success.value = false;
36
+ text.value = '';
37
+ const resp = await instanceService.createInstance('Default', false, true, true);
38
+ success.value = resp.success;
39
+ text.value = resp.text;
40
+ if (resp.success) {
41
+ timer = window.setTimeout(getStatistics, 500);
42
+ }
43
+ }
44
+ async function onCancel() {
45
+ if (timer >= 0) {
46
+ window.clearTimeout(timer); timer = -1;
47
+ }
48
+ success.value = false;
49
+ text.value = '';
50
+ const resp = await instanceService.cancelScan();
51
+ success.value = resp.success;
52
+ text.value = resp.text;
53
+ }
54
+ async function onClear() {
55
+ if (timer >= 0) {
56
+ window.clearTimeout(timer); timer = -1;
57
+ }
58
+ success.value = false;
59
+ text.value = '';
60
+ const resp = await instanceService.clearInstance();
61
+ success.value = resp.success;
62
+ text.value = resp.text;
63
+ }
64
+ async function onMakeCurrent() {
65
+ if (timer >= 0) {
66
+ window.clearTimeout(timer); timer = -1;
67
+ }
68
+ success.value = false;
69
+ text.value = '';
70
+ const resp = await instanceService.makeCurrent();
71
+ success.value = resp.success;
72
+ text.value = resp.text;
73
+ if (resp.success) {
74
+ await instanceService.getInformation();
75
+ }
76
+ }
77
+ </script>
78
+
79
+ <template>
80
+ <v-container fluid class="app-container app-scroll bg-light text-on-light" :style="heightStyle">
81
+ <h2>Media Instance Administration</h2>
82
+ <v-row dense class="font-weight-bold">
83
+ <v-col cols="4">Name</v-col>
84
+ <v-col cols="8">{{info.instanceName}}</v-col>
85
+ </v-row>
86
+ <v-row dense class="font-weight-bold">
87
+ <v-col cols="4">IP Address</v-col>
88
+ <v-col cols="8">{{info.ipAddress}}</v-col>
89
+ </v-row>
90
+ <v-row dense class="font-weight-bold">
91
+ <v-col cols="4">Number of list items</v-col>
92
+ <v-col cols="8">{{info.listItemCount}}</v-col>
93
+ </v-row>
94
+ <v-row dense class="font-weight-bold">
95
+ <v-col cols="4">List item date</v-col>
96
+ <v-col cols="8">{{listtime}}</v-col>
97
+ </v-row>
98
+ <v-row dense class="font-weight-bold">
99
+ <v-col cols="4">Root date</v-col>
100
+ <v-col cols="8">{{roottime}}</v-col>
101
+ </v-row>
102
+ <h4>Roots</h4>
103
+ <v-row dense class="font-weight-bold">
104
+ <v-col cols="1">Name</v-col>
105
+ <v-col cols="1">Id</v-col>
106
+ <v-col cols="1">Folders</v-col>
107
+ <v-col cols="1">V-Folders</v-col>
108
+ <v-col cols="2">Files</v-col>
109
+ <v-col cols="2">Modified at</v-col>
110
+ <v-col cols="4">Item types</v-col>
111
+ </v-row>
112
+ <root-information-line v-for="root in info.rootInformations" :key="root.id" :info="root"></root-information-line>
113
+
114
+ <v-row dense class="font-weight-bold">
115
+ <v-col cols="3">
116
+ <v-btn @click="onCreate">CREATE</v-btn>
117
+ </v-col>
118
+ <v-col cols="3">
119
+ <v-btn @click="onCancel">CANCEL</v-btn>
120
+ </v-col>
121
+ <v-col cols="3">
122
+ <v-btn @click="onClear">CLEAR</v-btn>
123
+ </v-col>
124
+ <v-col cols="3">
125
+ <v-btn @click="onMakeCurrent">MAKE CURRENT</v-btn>
126
+ </v-col>
127
+ </v-row>
128
+ <v-row dense class="font-weight-bold">
129
+ <v-col cols="2" v-show="stat.isScanning">SCANNING</v-col>
130
+ <v-col cols="2" v-show="stat.isCanceled">CANCELED</v-col>
131
+ <v-col cols="4">{{stat.listItemCount + ' list items'}}</v-col>
132
+ <v-col cols="1" v-show="success">OK</v-col>
133
+ <v-col cols="1" v-show="!success">NOT OK</v-col>
134
+ <v-col cols="3">{{text}}</v-col>
135
+ </v-row>
136
+ <h4>Statistics</h4>
137
+ <v-row dense class="font-weight-bold">
138
+ <v-col cols="1">Name</v-col>
139
+ <v-col cols="5">Directory</v-col>
140
+ <v-col cols="1"># Dirs</v-col>
141
+ <v-col cols="1">Folders</v-col>
142
+ <v-col cols="1">V-Folders</v-col>
143
+ <v-col cols="1">Files</v-col>
144
+ <v-col cols="1">Medias</v-col>
145
+ <v-col cols="1">Errors</v-col>
146
+ </v-row>
147
+ <root-statistics-line v-for="root in stat.rootStatistics" :key="root.rootName" :stat="root"></root-statistics-line>
148
+ </v-container>
149
+ </template>
@@ -0,0 +1,234 @@
1
+ <script setup lang="ts">
2
+ import { inject, ref, reactive, computed, onMounted, onUnmounted, nextTick, watch } from 'vue';
3
+ import { IAppState } from '@christianriedl/utils';
4
+ import { EItemType, EMediaType, IMediaFolder, IMediaItem, IAudioFile, MediaService } from '@christianriedl/media';
5
+
6
+ const appState = inject<IAppState>('appstate')!;
7
+ const getMediaService = inject<() => MediaService>('get-media')!;
8
+ const mediaService = getMediaService();
9
+ const heightStyle = computed(() => { return { height: appState.bodyHeight.value + 'px', overflowY: 'auto' } });
10
+ const isMobile = appState.isMobile;
11
+
12
+ const items: IMediaItem[] = reactive([]);
13
+ const selected = ref<IMediaItem>({ Name: 'Root', ItemType: EItemType.Root } as IMediaFolder);
14
+ const playingTrackUrl = ref("");
15
+ const playingTrack = ref<IAudioFile | null>(null);
16
+ const playItems: IAudioFile[] = [];
17
+ const itemIndex = ref(0);
18
+ const position = ref(0);
19
+ const positionLength = ref(0);
20
+ const listHeight = ref(0);
21
+ const playIndex = ref(-1);
22
+
23
+ const backVisible = computed(() => selected.value.ItemType != EItemType.AudioRoot);
24
+ const downloadAlbumVisible = computed(() => selected.value.ItemType == EItemType.AudioAlbum && !playingTrackUrl.value && !isMobile);
25
+ const downloadVisible = computed(() => playingTrackUrl.value && !isMobile);
26
+ const audio = ref<HTMLAudioElement | null>(null);
27
+ const paused = ref(false);
28
+ const listhead = ref<any>(null);
29
+ const positionText = computed(() => {
30
+ if (playingTrack.value) {
31
+ if (playingTrack.value.Duration > 0)
32
+ return to_time(position.value) + '/' + to_time(playingTrack.value.Duration);
33
+ else
34
+ return to_time(position.value);
35
+ }
36
+ return '';
37
+ });
38
+ window.addEventListener('popstate', onPopState);
39
+ function onPopState (event: any) {
40
+ if (event.state && event.state.noBackExitsApp && backVisible.value) {
41
+ event.preventDefault();
42
+ listBack();
43
+ }
44
+ }
45
+ onUnmounted(() => {
46
+ window.removeEventListener('popstate', onPopState);
47
+ while (window.history.state && window.history.state.noBackExitsApp)
48
+ window.history.back();
49
+ });
50
+ onMounted(async () => {
51
+ mediaService.log.trace('Music created');
52
+ const rc = await mediaService.getLists(EMediaType.Audio);
53
+ let root;
54
+ if (selected.value.ItemType == EItemType.AudioAlbum)
55
+ root = mediaService.folders[selected.value.DLNAParentID];
56
+ else
57
+ root = await mediaService.initializeAudios();
58
+ mediaService.log.trace('Music start with ' + root.DLNAID);
59
+ selected.value = root;
60
+ items.splice(0, items.length, ...root.Folders);
61
+ computeListHeight();
62
+ })
63
+ watch(appState.bodyHeight, () => computeListHeight());
64
+ function computeListHeight() {
65
+ nextTick(() => {
66
+ let height = appState.bodyHeight.value;
67
+ if (listhead.value && listhead.value._.vnode && listhead.value._.vnode.el) {
68
+ height = height - listhead.value._.vnode.el.clientHeight;
69
+ }
70
+ listHeight.value = height;
71
+ })
72
+ }
73
+ function listItem(item: IMediaItem) {
74
+ if (item.ItemType == EItemType.AudioTrack) {
75
+ //playItems = [];
76
+ playItems.splice(0, 0, ...items as IAudioFile[]);
77
+ play(items.indexOf(item));
78
+ computeListHeight();
79
+ }
80
+ else {
81
+ const folder = item as IMediaFolder;
82
+ if (folder.Folders && item.ItemType != EItemType.AudioAlbum) {
83
+ items.splice(0, items.length, ...folder.Folders);
84
+ selected.value = item;
85
+ window.history.pushState({ noBackExitsApp: true }, '')
86
+ computeListHeight();
87
+ }
88
+ else {
89
+ mediaService.getAudios(folder)
90
+ .then((audios) => {
91
+ window.history.pushState({ noBackExitsApp: true }, '')
92
+ selected.value = audios;
93
+ items.splice(0, items.length, ...audios.Files);
94
+ computeListHeight();
95
+ });
96
+ }
97
+ }
98
+ }
99
+ function listBack() {
100
+ const parent = mediaService.folders[selected.value.DLNAParentID];
101
+ selected.value = parent;
102
+ items.splice(0, items.length, ...parent.Folders);
103
+ computeListHeight();
104
+ }
105
+ function onTimeUpdate() {
106
+ position.value = audio.value!.currentTime;
107
+ if (playingTrack.value && playingTrack.value.Duration > 0)
108
+ positionLength.value = position.value * 100 / playingTrack.value.Duration;
109
+ else
110
+ positionLength.value = 0;
111
+ }
112
+ function onEnded() {
113
+ mediaService.log.trace(`onEnded ${playIndex} ended`);
114
+ if (playIndex.value < playItems.length - 1)
115
+ play(playIndex.value + 1);
116
+ }
117
+ function play(index: number) {
118
+ if (items.length == playItems.length && items[0].DLNAParentID == playItems[0].DLNAParentID)
119
+ itemIndex.value = index;
120
+ playIndex.value = index;
121
+ const item = playItems[playIndex.value];
122
+ paused.value = false;
123
+ playingTrack.value = item;
124
+ playingTrackUrl.value = mediaService.getAudioUrl(item);
125
+ audio.value!.src = playingTrackUrl.value;
126
+ audio.value!.load();
127
+ audio.value!.play()
128
+ .then(() => { mediaService.log.trace(`play ${index}ended`) });
129
+ }
130
+ function playpause() {
131
+ if (audio.value!.paused) {
132
+ paused.value = false;
133
+ audio.value!.play()
134
+ .then(() => { console.log("play ended") });
135
+ }
136
+ else {
137
+ paused.value = true;
138
+ audio.value!.pause();
139
+ }
140
+ }
141
+ function previous() {
142
+ if (playIndex.value > 0)
143
+ play(playIndex.value - 1);
144
+ }
145
+ function next() {
146
+ if (playIndex.value < playItems.length - 1)
147
+ play(playIndex.value + 1);
148
+ }
149
+ function download() {
150
+ window.open(playingTrackUrl.value);
151
+ }
152
+ function downloadAlbum() {
153
+ const downloadUrl = mediaService.getAlbumDownloadUrl(selected.value.Url);
154
+ window.open(downloadUrl);
155
+ }
156
+ function to_time(s: number): string {
157
+ var r = "";
158
+ s = Math.floor(s);
159
+ if (s >= 3600) {
160
+ r += Math.floor(s / 3600).toString() + ":"; s = s % 3600;
161
+ }
162
+ if (!r || s >= 600) {
163
+ r += Math.floor(s / 60).toString() + ":"; s = s % 60;
164
+ }
165
+ else {
166
+ r += "0"; r += Math.floor(s / 60).toString() + ":"; s = s % 60;
167
+ }
168
+ if (s >= 10) {
169
+ r += s.toString(); s = -1;
170
+ }
171
+ else if (s >= 0) {
172
+ r += "0"; r += s.toString();
173
+ s = -1;
174
+ }
175
+ return r;
176
+ }
177
+ </script>
178
+
179
+ <template>
180
+ <v-card ref="listhead" class="bg-media">
181
+ <audio ref="audio" preload="none" @ended="onEnded" @timeupdate="onTimeUpdate">
182
+ </audio>
183
+ <v-list-item three-line>
184
+ <v-list-item-avatar tile rounded="0" size="x-large" v-if="selected.ThumbnailUrl">
185
+ <img width="40" :src="selected.ThumbnailUrl">
186
+ </v-list-item-avatar>
187
+ <v-list-item-content>
188
+ <v-list-item-title>{{selected.title}}</v-list-item-title>
189
+ <v-list-item-subtitle>{{selected.subTitle}}</v-list-item-subtitle>
190
+ <v-list-item-subtitle v-if="playingTrack">{{playingTrack.Name}}</v-list-item-subtitle>
191
+ </v-list-item-content>
192
+ </v-list-item>
193
+ <v-card-actions>
194
+ <v-btn v-if="backVisible" @click="listBack">
195
+ <v-icon>{{$vuetify.icons.values.back}}</v-icon>
196
+ </v-btn>
197
+ <v-btn v-if="downloadAlbumVisible" @click="downloadAlbum">
198
+ Album
199
+ <v-icon>{{$vuetify.icons.values.download}}</v-icon>
200
+ </v-btn>
201
+ <v-btn small v-if="playingTrackUrl" @click="playpause">
202
+ <v-icon>{{paused ? $vuetify.icons.values.play : $vuetify.icons.values.pause }}</v-icon>
203
+ </v-btn>
204
+ <v-btn small v-if="playingTrackUrl" @click="previous">
205
+ <v-icon>{{$vuetify.icons.values.prev}}</v-icon>
206
+ </v-btn>
207
+ <v-btn small v-if="playingTrackUrl" @click="next">
208
+ <v-icon>{{$vuetify.icons.values.next}}</v-icon>
209
+ </v-btn>
210
+ <v-btn small v-if="downloadVisible" @click="download">
211
+ <v-icon>{{$vuetify.icons.values.download}}</v-icon>
212
+ </v-btn>
213
+ </v-card-actions>
214
+ <v-progress-linear v-if="playingTrackUrl" v-model="positionLength" color="blue" height="25"><strong>{{positionText}}</strong></v-progress-linear>
215
+ </v-card>
216
+ <v-card :height="listHeight" class="overflow-y-auto bg-media">
217
+ <v-list two-line class="bg-media">
218
+ <v-list-item-group v-model="itemIndex" >
219
+ <v-list-item v-for="(item,index) in items" :key="item.DLNAID" :title="item.title" :subtitle="item.subTitle"
220
+ active-color="blue" :active="index == playIndex" @click="listItem(item)">
221
+ <!--
222
+ <v-list-item-content>
223
+ <v-list-item-title v-html="item.title"></v-list-item-title>
224
+ <v-list-item-subtitle v-html="item.subTitle"></v-list-item-subtitle>
225
+ </v-list-item-content>
226
+ -->
227
+ <v-list-item-avatar right rounded="0" v-if="item.ThumbnailUrl">
228
+ <v-img width="64" :src="item.ThumbnailUrl"></v-img>
229
+ </v-list-item-avatar>
230
+ </v-list-item>
231
+ </v-list-item-group>
232
+ </v-list>
233
+ </v-card>
234
+ </template>
@@ -0,0 +1,255 @@
1
+ <script setup lang="ts">
2
+ import { inject, ref, reactive, computed, onMounted, onBeforeMount, onUnmounted, nextTick, watch } from 'vue';
3
+ import { useRouter, useRoute } from 'vue-router';
4
+ import { IAppState, Dictionary, Helper } from '@christianriedl/utils';
5
+ import { EMediaType, IPictureFile, MediaService } from '@christianriedl/media';
6
+
7
+ const router = useRouter();
8
+ const route = useRoute();
9
+ const appState = inject<IAppState>('appstate')!;
10
+ const getMediaService = inject<() => MediaService>('get-media')!;
11
+ const mediaService = getMediaService();
12
+ const heightStyle = computed(() => { return { height: appState.bodyHeight.value + 'px', overflowY: 'hidden' } });
13
+ const isMobile = appState.isMobile;
14
+ const url = ref('');
15
+ const dialog = ref(false);
16
+ const folderId = decodeURIComponent(route.query!.id!.toString());
17
+ const index = ref(0);
18
+ const items = reactive<IPictureFile[]>([]);
19
+ const width = ref(0);
20
+ const height = ref(0);
21
+ const landscape = ref(true);
22
+ const metadata = reactive<Dictionary<any>>({});
23
+ const cycle = ref(false);
24
+ const interval = ref(8000);
25
+
26
+ watch([appState.bodyHeight, appState.pageWidth], () => {
27
+ width.value = appState.pageWidth.value;
28
+ height.value = appState.bodyHeight.value;
29
+ landscape.value = width.value > height.value;
30
+ }, { immediate: true });
31
+
32
+ async function start(): Promise<boolean> {
33
+ window.document.addEventListener('keydown', onKey);
34
+ let folder = mediaService.folders[folderId];
35
+ if(!folder) {
36
+ const split = folderId.split(/[.|]/);
37
+ await mediaService.initializePhotos(split[1], split[2]);
38
+ folder = mediaService.folders[folderId];
39
+ if (folder) {
40
+ if (!mediaService.medialists["Picture.Event"])
41
+ await mediaService.getLists(EMediaType.Picture);
42
+ }
43
+ else
44
+ return false;
45
+ }
46
+ const photos = await mediaService.getPhotos(folder);
47
+ if(photos.Files.length > 0) {
48
+ items.splice(0, items.length, ...photos.Files as IPictureFile[]);
49
+ if (route.query.start) {
50
+ const start = route.query.start.toString();
51
+ const idx = items.findIndex((item) => item.DLNAID == start);
52
+ if (idx >= 0) {
53
+ index.value = idx;
54
+ }
55
+ }
56
+ url.value = photos.Url;
57
+ buildUrls();
58
+ //onInput(0); required für v-carousel ?
59
+ }
60
+ else {
61
+ nextTick(() => router.back());
62
+ }
63
+ return true;
64
+ }
65
+ onMounted(async () => {
66
+ await start();
67
+ });
68
+ onUnmounted(() => {
69
+ window.document.removeEventListener('keydown', onKey);
70
+ });
71
+
72
+ function buildUrls() {
73
+ for (var i = 0; i < items.length; i++) {
74
+ const item = items[i];
75
+ if (item.realUrl && item.realUrl.startsWith('blob'))
76
+ window.URL.revokeObjectURL(item.realUrl);
77
+ item.realUrl = getUrl(item);
78
+ }
79
+ }
80
+ function getUrl(item: IPictureFile) {
81
+ if (landscape.value)
82
+ return mediaService.getPhotoUrl(url.value, item.Url, `${0}x${height.value}x${item.Orientation}`);
83
+ else
84
+ return mediaService.getPhotoUrl(url.value, item.Url, `${width.value}x${0}x${item.Orientation}`);
85
+ }
86
+ function onInput(idx: number) {
87
+ idx++;
88
+ if (idx < items.length) {
89
+ const item = items[idx];
90
+ mediaService.getImage(item.realUrl!)
91
+ .then(blob => {
92
+ item.realUrl = window.URL.createObjectURL(blob);
93
+ });
94
+ }
95
+ }
96
+ function onClick(ev: Event) {
97
+ dialog.value = true;
98
+ }
99
+ async function onKey(ev: KeyboardEvent) {
100
+ switch (ev.code) {
101
+ case "Escape":
102
+ router.back();
103
+ break;
104
+ case "ArrowLeft":
105
+ if (index.value > 0)
106
+ index.value--;
107
+ break;
108
+ case "ArrowRight":
109
+ if (index.value < items.length - 1)
110
+ index.value++;
111
+ break;
112
+ case "KeyI":
113
+ const item = items[index.value];
114
+ const exif = await mediaService.getExifInfo(item.DLNAParentID, item.DLNAID);
115
+ setMetaData(exif, item);
116
+ dialog.value = true;
117
+ break;
118
+ case "KeyP":
119
+ cycle.value = true;
120
+ break;
121
+ case "Digit0":
122
+ cycle.value = false;
123
+ break;
124
+ case "Digit1":
125
+ cycle.value = true; interval.value = 1000;
126
+ break;
127
+ case "Digit2":
128
+ cycle.value = true; interval.value = 2000;
129
+ break;
130
+ case "Digit3":
131
+ cycle.value = true; interval.value = 3000;
132
+ break;
133
+ case "Digit4":
134
+ cycle.value = true; interval.value = 4000;
135
+ break;
136
+ case "Digit5":
137
+ cycle.value = true; interval.value = 5000;
138
+ break;
139
+ case "Digit6":
140
+ cycle.value = true; interval.value = 6000;
141
+ break;
142
+ case "Digit7":
143
+ cycle.value = true; interval.value = 7000;
144
+ break;
145
+ case "Digit8":
146
+ cycle.value = true; interval.value = 8000;
147
+ break;
148
+ case "Digit9":
149
+ cycle.value = true; interval.value = 9000;
150
+ break;
151
+ }
152
+ }
153
+ function itemWidth(item: IPictureFile): number {
154
+ const fac = item.Height / height.value;
155
+ return Math.floor(item.Width * fac);
156
+ }
157
+ function aspectRatio(item: IPictureFile): string {
158
+ return `${item.Width}/${item.Height}`;
159
+ }
160
+ function setMetaData(exif: Dictionary<any>, file: IPictureFile) {
161
+ metadata['Name'] = file.Name;
162
+ if (exif) {
163
+ if (exif.Model)
164
+ metadata['Kamera'] = exif.Model;
165
+ else
166
+ delete metadata['Kamera'];
167
+ if (exif.LensModel)
168
+ metadata['Objectiv'] = exif.LensModel;
169
+ else
170
+ delete metadata['Objectiv'];
171
+ if (exif.Artist)
172
+ metadata['Von'] = exif.Artist as string;
173
+ else
174
+ delete metadata['Von'];
175
+ if (exif.ExposureTime)
176
+ metadata['Belichtung'] = '1/' + Helper.round(1.0 / exif.ExposureTime, 0);
177
+ else
178
+ delete metadata['Belichtung'];
179
+ if (exif.FNumber)
180
+ metadata['Blende'] = exif.FNumber.toString();
181
+ else
182
+ delete metadata['Blende'];
183
+ if (exif.DateTimeOriginal)
184
+ metadata['Zeit'] = exif.DateTimeOriginal;
185
+ else
186
+ metadata['Zeit'] = file.Date.toLocaleString();
187
+ }
188
+ else {
189
+ delete metadata['Kamera'];
190
+ delete metadata['Objectiv'];
191
+ delete metadata['Von'];
192
+ delete metadata['Belichtung'];
193
+ delete metadata['Blende'];
194
+ metadata['Zeit'] = file.Date.toLocaleString();
195
+ }
196
+
197
+ metadata['Dimension'] = `${file.Width}x${file.Height} o:${file.Orientation}`
198
+ metadata['Mb'] = (file.Size / 1000000).toString() + " Mb";
199
+ if (file.EventIdx >= 0)
200
+ metadata['Ereignis'] = mediaService.getListEntry("Picture.Event", file.EventIdx);
201
+ else
202
+ delete metadata['Ereignis'];
203
+ if (file.LocationIdx >= 0)
204
+ metadata['Ort'] = mediaService.getListEntry("Picture.Location", file.LocationIdx);
205
+ else
206
+ delete metadata['Ort'];
207
+ if (file.PersonIdxs && file.PersonIdxs.length > 0) {
208
+ let persons = "";
209
+ for (let i = 0; i < file.PersonIdxs.length; i++) {
210
+ persons = persons + mediaService.getListEntry("Picture.Person", file.PersonIdxs[i]) + " ";
211
+ }
212
+ metadata['Personen'] = persons;
213
+ }
214
+ else
215
+ delete metadata['Personen'];
216
+ }
217
+ </script>
218
+
219
+ <template>
220
+ <v-container fluid :style="heightStyle">
221
+ <v-carousel hide-delimiters :show-arrows="false" v-model="index"
222
+ :width="width" :height="height" :cycle="cycle" :interval="interval"
223
+ @change="onInput">
224
+ <v-carousel-item v-for="item in items" :key="item.DLNAID">
225
+ <img :src="item.realUrl" />
226
+ </v-carousel-item>
227
+ </v-carousel>
228
+ <v-dialog v-model="dialog" :fullscreen="isMobile">
229
+ <v-card v-if="dialog" width="400">
230
+ <v-card-title class="headline">Metadata</v-card-title>
231
+ <v-card-text>
232
+ <v-container>
233
+ <v-row v-for="(value, key) in metadata" :key="key">
234
+ <v-col cols="4" class="font-weight-bold">{{key}}</v-col>
235
+ <v-col cols="8">{{value}}</v-col>
236
+ </v-row>
237
+ </v-container>
238
+ </v-card-text>
239
+ <v-card-actions>
240
+ <v-btn @click="dialog = false">
241
+ <v-icon large>{{$vuetify.icons.values.stop}}</v-icon>Exit
242
+ </v-btn>
243
+ </v-card-actions>
244
+ </v-card>
245
+ </v-dialog>
246
+ </v-container>
247
+ </template>
248
+
249
+ <style scoped>
250
+ img {
251
+ display: block;
252
+ margin-left: auto;
253
+ margin-right: auto;
254
+ }
255
+ </style>