@saooti/octopus-sdk 41.8.4 → 41.9.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.
- package/.claude/settings.local.json +2 -1
- package/CHANGELOG.md +64 -0
- package/eslint-config.d.ts +3 -0
- package/eslint-config.mjs +43 -0
- package/eslint.config.mjs +2 -43
- package/index.ts +16 -0
- package/package.json +14 -2
- package/src/api/transcriptionApi.ts +129 -3
- package/src/components/composable/player/usePlayerDisplayTime.ts +1 -0
- package/src/components/composable/player/usePlayerTranscript.ts +66 -17
- package/src/components/composable/podcasts/usePodcastView.ts +2 -3
- package/src/components/composable/share/useSharePath.ts +1 -1
- package/src/components/composable/share/useSharePlatforms.ts +16 -16
- package/src/components/composable/useDayjs.ts +24 -0
- package/src/components/composable/useRights.ts +11 -1
- package/src/components/composable/useTranslation.ts +175 -0
- package/src/components/display/comments/CommentName.vue +1 -0
- package/src/components/display/comments/modal/CheckIdentityModal.vue +1 -0
- package/src/components/display/comments/modal/ReportAbuseModal.vue +1 -0
- package/src/components/display/emission/EmissionInlineList.vue +7 -9
- package/src/components/display/filter/ProductorSearch.vue +1 -1
- package/src/components/display/list/SwiperList.vue +1 -0
- package/src/components/display/organisation/OrganisationChooser.vue +1 -1
- package/src/components/display/organisation/OrganisationChooserLight.vue +1 -1
- package/src/components/display/participant/ParticipantInlineList.vue +14 -13
- package/src/components/display/podcasts/PodcastItemInfo.vue +97 -59
- package/src/components/display/podcasts/PodcastModuleBox.vue +42 -5
- package/src/components/display/podcasts/PodcastRawTranscript.vue +166 -81
- package/src/components/display/sharing/SharePlayer.vue +1 -1
- package/src/components/form/ClassicDatePicker.vue +8 -3
- package/src/components/form/ClassicInputText.vue +12 -2
- package/src/components/form/ClassicLoading.vue +6 -2
- package/src/components/form/ClassicSelect.vue +2 -2
- package/src/components/misc/ClassicAlert.vue +26 -14
- package/src/components/misc/ClassicSpinner.vue +18 -4
- package/src/components/misc/FooterSection.vue +1 -1
- package/src/components/misc/player/PlayerComponent.vue +1 -1
- package/src/components/misc/player/PlayerLarge.vue +14 -0
- package/src/components/pages/EmissionPage.vue +22 -5
- package/src/components/pages/LivesPage.vue +1 -1
- package/src/helper/language.ts +38 -0
- package/src/i18n.ts +0 -7
- package/src/locale/de.json +3 -1
- package/src/locale/en.json +3 -1
- package/src/locale/es.json +3 -1
- package/src/locale/fr.json +4 -1
- package/src/locale/it.json +3 -1
- package/src/locale/sl.json +3 -1
- package/src/main.ts +3 -29
- package/src/stores/AuthStore.ts +1 -1
- package/src/stores/ParamSdkStore.ts +10 -3
- package/src/stores/class/general/emission.ts +4 -1
- package/src/stores/class/general/organisation.ts +10 -1
- package/src/stores/class/general/podcast.ts +6 -1
- package/src/stores/class/transcript/transcriptParams.ts +25 -0
- package/tests/api/transcriptionApi.spec.ts +58 -0
- package/tests/components/composable/player/usePlayerTranscript.spec.ts +238 -0
- package/tests/components/composable/useRights.spec.ts +44 -2
- package/tests/components/composable/useTranslation.spec.ts +144 -0
- package/tests/components/display/podcasts/PodcastFilterList.spec.ts +4 -4
- package/tests/components/display/podcasts/PodcastItemInfo.spec.ts +65 -0
- package/tests/components/display/podcasts/PodcastModuleBox.spec.ts +64 -0
- package/tests/components/display/podcasts/PodcastRawTranscript.spec.ts +253 -0
- package/tests/components/misc/player/PlayerLarge.spec.ts +71 -0
- package/tests/components/pages/EmissionPage.spec.ts +31 -0
- package/tests/helper/language.spec.ts +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 41.9.1 (07/04/2026)
|
|
4
|
+
|
|
5
|
+
**Features**
|
|
6
|
+
|
|
7
|
+
- **12535** - Améliorations pour la traduction
|
|
8
|
+
- La régénration de la transcription invalide les données de
|
|
9
|
+
`PodcastRawTranscript`
|
|
10
|
+
- **14406** - Ajout de `useDayjs` qui permet d'avoir des dates réactives
|
|
11
|
+
- Mise à jour des droits relatifs aux transcriptions/traductions
|
|
12
|
+
- Ajout de nouveaux endpoints à `transcriptionApi`
|
|
13
|
+
- Ajout nouveaux exports:
|
|
14
|
+
- composable `useTranslation`
|
|
15
|
+
- types `TranslationData`, `ModifyPodcastConfig`
|
|
16
|
+
- énumération `TranslationState`
|
|
17
|
+
- Améliorations composants :
|
|
18
|
+
- `ClassicInputText` en mode `textarea` scrolle en haut du contenu par défaut
|
|
19
|
+
- `ClassicAlert` a maintenant un props `text` qui enlève le font et la bordure
|
|
20
|
+
|
|
21
|
+
**Fixes**
|
|
22
|
+
|
|
23
|
+
- **14330** - La page d'émission propose uniquement le dernier épisode
|
|
24
|
+
**valide** à la lecture
|
|
25
|
+
|
|
26
|
+
## 41.9.0 (31/03/2026)
|
|
27
|
+
|
|
28
|
+
**Features**
|
|
29
|
+
|
|
30
|
+
- **12535** - Implémentation de la traduction des sous-titres
|
|
31
|
+
- Mise en place de l'api `transcriptionApi` pour simplifier les appels
|
|
32
|
+
- Ajout du composable `useTranslation` pour regrouper les opérations relatives
|
|
33
|
+
aux traductions
|
|
34
|
+
- `PodcastRawTranscript` permet maintenant de choisir la langue de la
|
|
35
|
+
transcription
|
|
36
|
+
- La langue la plus pertinente pour l'utilisateur est sélectionnée par
|
|
37
|
+
défaut
|
|
38
|
+
- La transcription affichée dans le player correspond à la langue la plus
|
|
39
|
+
pertinente disponible
|
|
40
|
+
- **14357** - Affichage des sous-titres et résumés d'épisodes, et des
|
|
41
|
+
sous-titres des émissions
|
|
42
|
+
- Ajout de nouveaux paramètres pour contrôler cet affichage :
|
|
43
|
+
- `hideSubtitle` dans `emissionPage` & `podcastPage`, pour cacher les
|
|
44
|
+
sous-titres
|
|
45
|
+
- `descriptionOrSummary` dans `emissionPage`, pour choisir quel élément
|
|
46
|
+
afficher
|
|
47
|
+
- **14387** - Affichage du numéro de saison et d'épisode, ainsi que du type
|
|
48
|
+
d'épisode dans les `PodcastItemInfo`
|
|
49
|
+
- La configuration eslint est maintenant exportée, elle peut donc être intégrée
|
|
50
|
+
telle quelle dans les projets se basant sur le SDK
|
|
51
|
+
- Ajout d'options de configuration pour `ClassicLoading` et `ClassicSpinner`
|
|
52
|
+
|
|
53
|
+
**Fix**
|
|
54
|
+
|
|
55
|
+
- **14404** - Correction affichage de la transcription dans le cas de preroll/
|
|
56
|
+
postrolls multiples
|
|
57
|
+
- Correction `z-index` des boutons de `SwiperList`
|
|
58
|
+
- La propriété `focus` de `ClassicInputText` est à `false` par défaut
|
|
59
|
+
|
|
60
|
+
**Misc**
|
|
61
|
+
|
|
62
|
+
- Correction warning avec les icônes de `useSharePlatforms`
|
|
63
|
+
- Ajustement composants pour les podcastmakers
|
|
64
|
+
- Mise à jour des dépendances
|
|
65
|
+
- Les exports d'organisation viennent du SDK et non plus du package parent
|
|
66
|
+
|
|
3
67
|
## 41.8.4 (24/03/2026)
|
|
4
68
|
|
|
5
69
|
**Fix**
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import eslintPluginVue from 'eslint-plugin-vue';
|
|
3
|
+
import globals from 'globals';
|
|
4
|
+
import typescriptEslint from 'typescript-eslint';
|
|
5
|
+
|
|
6
|
+
export default typescriptEslint.config(
|
|
7
|
+
{ ignores: ['*.d.ts', '**/coverage', '**/dist'] },
|
|
8
|
+
{
|
|
9
|
+
extends: [
|
|
10
|
+
eslint.configs.recommended,
|
|
11
|
+
...typescriptEslint.configs.recommended,
|
|
12
|
+
...eslintPluginVue.configs['flat/recommended'],
|
|
13
|
+
],
|
|
14
|
+
files: ['**/*.{ts,vue}'],
|
|
15
|
+
languageOptions: {
|
|
16
|
+
ecmaVersion: 'latest',
|
|
17
|
+
sourceType: 'module',
|
|
18
|
+
globals: globals.browser,
|
|
19
|
+
parserOptions: {
|
|
20
|
+
parser: typescriptEslint.parser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
rules: {
|
|
24
|
+
// your rules
|
|
25
|
+
"curly": ['error'],
|
|
26
|
+
|
|
27
|
+
// Please don't use console statements and duplicated imports, TODOs are marked
|
|
28
|
+
"no-console": ['warn', { allow: ['warn', 'error'] }],
|
|
29
|
+
"no-warning-comments": ['warn'],
|
|
30
|
+
"no-duplicate-imports": ['warn'],
|
|
31
|
+
|
|
32
|
+
// Prevent errors when testing on refs (instead of value of ref)
|
|
33
|
+
"vue/no-ref-as-operand": ['error'],
|
|
34
|
+
|
|
35
|
+
// Indentation
|
|
36
|
+
"vue/html-indent": ['warn', 4],
|
|
37
|
+
"vue/script-indent": ['warn', 4],
|
|
38
|
+
|
|
39
|
+
// Number of attributes per line (increase because sometimes two is not a lot)
|
|
40
|
+
"vue/max-attributes-per-line": ['warn', { singleline: 2 } ]
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
);
|
package/eslint.config.mjs
CHANGED
|
@@ -1,43 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import globals from 'globals';
|
|
4
|
-
import typescriptEslint from 'typescript-eslint';
|
|
5
|
-
|
|
6
|
-
export default typescriptEslint.config(
|
|
7
|
-
{ ignores: ['*.d.ts', '**/coverage', '**/dist'] },
|
|
8
|
-
{
|
|
9
|
-
extends: [
|
|
10
|
-
eslint.configs.recommended,
|
|
11
|
-
...typescriptEslint.configs.recommended,
|
|
12
|
-
...eslintPluginVue.configs['flat/recommended'],
|
|
13
|
-
],
|
|
14
|
-
files: ['**/*.{ts,vue}'],
|
|
15
|
-
languageOptions: {
|
|
16
|
-
ecmaVersion: 'latest',
|
|
17
|
-
sourceType: 'module',
|
|
18
|
-
globals: globals.browser,
|
|
19
|
-
parserOptions: {
|
|
20
|
-
parser: typescriptEslint.parser,
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
rules: {
|
|
24
|
-
// your rules
|
|
25
|
-
"curly": ['error'],
|
|
26
|
-
|
|
27
|
-
// Please don't use console statements and duplicated imports, TODOs are marked
|
|
28
|
-
"no-console": ['warn', { allow: ['warn', 'error'] }],
|
|
29
|
-
"no-warning-comments": ['warn'],
|
|
30
|
-
"no-duplicate-imports": ['warn'],
|
|
31
|
-
|
|
32
|
-
// Prevent errors when testing on refs (instead of value of ref)
|
|
33
|
-
"vue/no-ref-as-operand": ['error'],
|
|
34
|
-
|
|
35
|
-
// Indentation
|
|
36
|
-
"vue/html-indent": ['warn', 4],
|
|
37
|
-
"vue/script-indent": ['warn', 4],
|
|
38
|
-
|
|
39
|
-
// Number of attributes per line (increase because sometimes two is not a lot)
|
|
40
|
-
"vue/max-attributes-per-line": ['warn', { singleline: 2 } ]
|
|
41
|
-
},
|
|
42
|
-
}
|
|
43
|
-
);
|
|
1
|
+
import sdkConfig from './eslint-config.mjs';
|
|
2
|
+
export default sdkConfig;
|
package/index.ts
CHANGED
|
@@ -135,6 +135,7 @@ export { useSharePath } from "./src/components/composable/share/useSharePath.ts"
|
|
|
135
135
|
export { useOrgaComputed } from "./src/components/composable/useOrgaComputed.ts";
|
|
136
136
|
export { useSeoTitleUrl } from "./src/components/composable/route/useSeoTitleUrl.ts";
|
|
137
137
|
export { useSeasonsManagement } from "./src/components/composable/useSeasonsManagement.ts";
|
|
138
|
+
export { useTranslation } from "./src/components/composable/useTranslation.ts";
|
|
138
139
|
|
|
139
140
|
//helper
|
|
140
141
|
import domHelper from "./src/helper/domHelper.ts";
|
|
@@ -161,6 +162,7 @@ import classicApi from "./src/api/classicApi.ts";
|
|
|
161
162
|
|
|
162
163
|
// API
|
|
163
164
|
export { emissionApi } from "./src/api/emissionApi.ts";
|
|
165
|
+
export { transcriptionApi, type TranslationData, TranslationState } from "./src/api/transcriptionApi.ts";
|
|
164
166
|
export * from "./src/api/groupsApi.ts";
|
|
165
167
|
export { organisationApi } from "./src/api/organisationApi.ts";
|
|
166
168
|
export { playlistApi } from "./src/api/playlistApi.ts";
|
|
@@ -168,9 +170,23 @@ export { podcastApi, PodcastSort, type PodcastSearchOptions } from "./src/api/po
|
|
|
168
170
|
|
|
169
171
|
// Types
|
|
170
172
|
export { type Emission, SeasonMode, emptyEmissionData } from "./src/stores/class/general/emission.ts";
|
|
173
|
+
export { type Organisation, type OrganisationAttributes, emptyOrganisationData, emptyOrgaData } from "./src/stores/class/general/organisation.ts";
|
|
171
174
|
export { type Podcast, type PodcastAvailability, PodcastType } from "./src/stores/class/general/podcast.ts";
|
|
172
175
|
export { type Playlist, type PlaylistRule } from "./src/stores/class/general/playlist.ts";
|
|
173
176
|
export { type Annotations } from "./src/stores/class/general";
|
|
177
|
+
export {
|
|
178
|
+
CreateTranslation,
|
|
179
|
+
type TranslationConfiguration,
|
|
180
|
+
defaultTranslationConfig,
|
|
181
|
+
defaultModifyPodcastConfig,
|
|
182
|
+
defaultTtsParams,
|
|
183
|
+
type ModifyPodcastConfig,
|
|
184
|
+
ModifyPodcastEnum,
|
|
185
|
+
type TranscriptParams,
|
|
186
|
+
type TtsParams,
|
|
187
|
+
type ProviderTts,
|
|
188
|
+
type Voice
|
|
189
|
+
} from "./src/stores/class/transcript/transcriptParams.ts";
|
|
174
190
|
|
|
175
191
|
//Icons
|
|
176
192
|
export const getAmazonMusicIcon = () => import("./src/components/icons/AmazonMusicIcon.vue");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saooti/octopus-sdk",
|
|
3
|
-
"version": "41.
|
|
3
|
+
"version": "41.9.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Javascript SDK for using octopus",
|
|
6
6
|
"author": "Saooti",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"bundle": "vite-bundle-visualizer",
|
|
13
13
|
"proxy_authentifié": "node proxy.ts",
|
|
14
14
|
"proxy_non_authentifié": "node proxy.ts false",
|
|
15
|
-
"lint": "eslint --fix
|
|
15
|
+
"lint": "eslint --fix --max-warnings 0",
|
|
16
16
|
"stylelint": "stylelint **/*.{scss,vue} --fix",
|
|
17
17
|
"sonar": "sonar -Dsonar.host.url=http://localhost:9000"
|
|
18
18
|
},
|
|
@@ -95,5 +95,17 @@
|
|
|
95
95
|
},
|
|
96
96
|
"peerDependencies": {
|
|
97
97
|
"pinia": ">=2.3.0"
|
|
98
|
+
},
|
|
99
|
+
"exports": {
|
|
100
|
+
".": {
|
|
101
|
+
"types": "./index.d.ts",
|
|
102
|
+
"default": "./index.ts"
|
|
103
|
+
},
|
|
104
|
+
"./src/*": "./src/*",
|
|
105
|
+
"./tests/*": "./tests/*",
|
|
106
|
+
"./eslint-config": {
|
|
107
|
+
"types": "./eslint-config.d.ts",
|
|
108
|
+
"default": "./eslint-config.mjs"
|
|
109
|
+
}
|
|
98
110
|
}
|
|
99
111
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ModifyPodcastConfig } from "../stores/class/transcript/transcriptParams";
|
|
2
|
+
import { ModuleApi } from "./apiConnection";
|
|
3
|
+
import classicApi from "./classicApi";
|
|
2
4
|
|
|
3
5
|
/** State of the translation */
|
|
4
6
|
export enum TranslationState {
|
|
@@ -30,6 +32,11 @@ export interface PodcastTranslationData {
|
|
|
30
32
|
translations: Array<TranslationData>;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
interface TranslationProgress {
|
|
36
|
+
started: boolean;
|
|
37
|
+
percent: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
/**
|
|
34
41
|
* Returns the translations defined on the podcast
|
|
35
42
|
* @param podcastId ID of the podcast
|
|
@@ -38,10 +45,129 @@ export interface PodcastTranslationData {
|
|
|
38
45
|
async function getTranslations(podcastId: number): Promise<PodcastTranslationData> {
|
|
39
46
|
return classicApi.fetchData<PodcastTranslationData>({
|
|
40
47
|
api: ModuleApi.SPEECHTOTEXT,
|
|
41
|
-
path:
|
|
48
|
+
path: `transcription/${podcastId}/languages`
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns the translation in a given language for the podcast
|
|
54
|
+
* @param podcastId ID of the podcast
|
|
55
|
+
* @param language The target language
|
|
56
|
+
* @param mayCreate *(optional)* If set to true, request creation if not available
|
|
57
|
+
* @returns The transcription
|
|
58
|
+
*/
|
|
59
|
+
async function getTranslation(podcastId: number, language: string, mayCreate?: boolean): Promise<string> {
|
|
60
|
+
let path = `transcription/${podcastId}/languages/${language}/srt`;
|
|
61
|
+
if (mayCreate !== undefined) {
|
|
62
|
+
path = `${path}?mayCreateIfNotExists=${mayCreate}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let found = false;
|
|
66
|
+
let timeout = 1000;
|
|
67
|
+
const timeoutStep = 100;
|
|
68
|
+
while (!found) {
|
|
69
|
+
const result = await classicApi.fetchData<string|TranslationProgress>({
|
|
70
|
+
api: ModuleApi.SPEECHTOTEXT,
|
|
71
|
+
path
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Stop when we get the proper result
|
|
75
|
+
if (typeof result === 'string') {
|
|
76
|
+
found = true;
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Wait some time before retrying
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
82
|
+
timeout += timeoutStep;
|
|
83
|
+
|
|
84
|
+
if (timeout >= 60 * 1000) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new Error('Timeout lors de la récupération de la transcription');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get raw transcription for the given podcast
|
|
94
|
+
* @param podcastId ID of the podcast
|
|
95
|
+
* @returns The raw transcript
|
|
96
|
+
*/
|
|
97
|
+
async function getRawTranscription(podcastId: number): Promise<string> {
|
|
98
|
+
return classicApi.fetchData({
|
|
99
|
+
api: ModuleApi.SPEECHTOTEXT,
|
|
100
|
+
path: `transcription/text/${podcastId}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Generate the transcription on the given podcast
|
|
106
|
+
* @param podcastId ID of the podcast on which to do the transcription
|
|
107
|
+
* @param langauge Target language. Should be the native language of the podcast
|
|
108
|
+
* @param params Transcription parameters
|
|
109
|
+
*/
|
|
110
|
+
async function generateTranscription(podcastId: number, language: string, params: ModifyPodcastConfig): Promise<void> {
|
|
111
|
+
await classicApi.putData({
|
|
112
|
+
api: ModuleApi.SPEECHTOTEXT,
|
|
113
|
+
path: `convert/${language}/${podcastId}`,
|
|
114
|
+
dataToSend: params
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Regenerate the transcription on the given podcast
|
|
120
|
+
* Currently does the same as `generateTranscription`, but should be used when
|
|
121
|
+
* relevant in case of future evolutions.
|
|
122
|
+
* @param podcastId ID of the podcast on which to do the transcription
|
|
123
|
+
* @param langauge Target language. Should be the native language of the podcast
|
|
124
|
+
* @param params Transcription parameters
|
|
125
|
+
*/
|
|
126
|
+
async function regenerateTranscription(podcastId: number, language: string, params: ModifyPodcastConfig): Promise<void> {
|
|
127
|
+
await classicApi.putData({
|
|
128
|
+
api: ModuleApi.SPEECHTOTEXT,
|
|
129
|
+
path: `regenerate/${language}/${podcastId}`,
|
|
130
|
+
dataToSend: params
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Change the visibility of the podcast
|
|
136
|
+
* @param podcastId ID of the podcast for which to change the visibility
|
|
137
|
+
* @param visibility New visibility state
|
|
138
|
+
*/
|
|
139
|
+
async function changeTranscriptionVisibility(podcastId: number, visibility: boolean): Promise<void> {
|
|
140
|
+
await classicApi.putData({
|
|
141
|
+
api: 11,
|
|
142
|
+
path: "visibility/" + podcastId,
|
|
143
|
+
parameters: {
|
|
144
|
+
visibility
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Update the transcription of a podcast
|
|
151
|
+
* @param podcastId ID of the podcast
|
|
152
|
+
* @param transcript The new transcript
|
|
153
|
+
*/
|
|
154
|
+
async function updateTranscription(podcastId: number, transcript: string): Promise<void> {
|
|
155
|
+
await classicApi.postData({
|
|
156
|
+
api: ModuleApi.SPEECHTOTEXT,
|
|
157
|
+
path: "update/" + podcastId,
|
|
158
|
+
dataToSend: {
|
|
159
|
+
file: new File([transcript], "file")
|
|
160
|
+
},
|
|
161
|
+
contentType: "formData"
|
|
42
162
|
});
|
|
43
163
|
}
|
|
44
164
|
|
|
45
165
|
export const transcriptionApi = {
|
|
46
|
-
|
|
166
|
+
changeTranscriptionVisibility,
|
|
167
|
+
getTranslations,
|
|
168
|
+
getTranslation,
|
|
169
|
+
getRawTranscription,
|
|
170
|
+
generateTranscription,
|
|
171
|
+
regenerateTranscription,
|
|
172
|
+
updateTranscription
|
|
47
173
|
};
|
|
@@ -2,45 +2,74 @@ import { usePlayerStore } from "../../../stores/PlayerStore";
|
|
|
2
2
|
import { useVastStore } from "../../../stores/VastStore";
|
|
3
3
|
import classicApi from "../../../api/classicApi";
|
|
4
4
|
import { AdserverOtherEmission } from "@/stores/class/adserver/adserverOtherEmission";
|
|
5
|
+
import { useTranslation } from "../useTranslation";
|
|
6
|
+
import { transcriptionApi } from "../../../api/transcriptionApi";
|
|
7
|
+
import { ref } from "vue";
|
|
8
|
+
|
|
9
|
+
/** Contains the language of the transcript being generated */
|
|
10
|
+
const generatingTranscriptLanguage = ref<string|null>(null);
|
|
11
|
+
|
|
5
12
|
export const usePlayerTranscript = ()=>{
|
|
6
13
|
|
|
7
14
|
const playerStore = usePlayerStore();
|
|
8
15
|
const vastStore = useVastStore();
|
|
16
|
+
const { getMostRelevantLanguage } = useTranslation();
|
|
9
17
|
|
|
10
18
|
async function checkDelaytWithStitching(){
|
|
11
19
|
playerStore.playerUpdateDelayStitching(0);
|
|
12
|
-
if(vastStore.useVastPlayerPodcast){
|
|
20
|
+
if(vastStore.useVastPlayerPodcast){
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
const audioPlayer = document.querySelector("#audio-player") as HTMLAudioElement;
|
|
14
25
|
if (!playerStore.playerTranscript || !audioPlayer || !playerStore.playerPodcast ||
|
|
15
|
-
audioPlayer.duration <= playerStore.playerPodcast.duration / 1000 + 5)
|
|
16
|
-
{
|
|
26
|
+
audioPlayer.duration <= playerStore.playerPodcast.duration / 1000 + 5) {
|
|
17
27
|
return;
|
|
18
28
|
}
|
|
29
|
+
|
|
19
30
|
const adserverConfig = await classicApi.fetchData<AdserverOtherEmission>({
|
|
20
31
|
api:0,
|
|
21
32
|
path:`ad/test/podcast/${playerStore.playerPodcast.podcastId}`,
|
|
22
33
|
isNotAuth:true
|
|
23
34
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
|
|
36
|
+
// In case of midroll ads, we can't properly display transcription, so
|
|
37
|
+
// we disable it.
|
|
38
|
+
const hasOtherThanPreOrPost = adserverConfig.config.doublets
|
|
39
|
+
.filter(doublet => !(["pre", "post"].includes(doublet.timing.insertion)))
|
|
40
|
+
.length > 0;
|
|
41
|
+
if (hasOtherThanPreOrPost) {
|
|
42
|
+
console.warn("This episode's ad settings doesn't allow for transcription");
|
|
30
43
|
playerStore.playerUpdateChaptering();
|
|
31
44
|
playerStore.playerUpdateTranscript();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// In case of preroll ads, delay start of transcription
|
|
49
|
+
const hasPre = adserverConfig.config.doublets
|
|
50
|
+
.filter(doublet => doublet.timing.insertion === "pre")
|
|
51
|
+
.length > 0;
|
|
52
|
+
if(hasPre) {
|
|
53
|
+
// Since we have the expected time (from playerStore) and effective time
|
|
54
|
+
// (from audioPlayer, which include ads), we just delay by the delta
|
|
55
|
+
playerStore.playerUpdateDelayStitching( audioPlayer.duration - (playerStore.playerPodcast.duration / 1000));
|
|
32
56
|
}
|
|
33
57
|
}
|
|
34
58
|
|
|
35
59
|
async function getTranscription(): Promise<void> {
|
|
60
|
+
generatingTranscriptLanguage.value = null;
|
|
36
61
|
if (!playerStore.playerPodcast) {
|
|
37
62
|
playerStore.playerUpdateTranscript();
|
|
38
63
|
return;
|
|
39
64
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
|
|
66
|
+
// Retrieve best language for transcription
|
|
67
|
+
const podcastId = playerStore.playerPodcast.podcastId;
|
|
68
|
+
const translationData = await transcriptionApi.getTranslations(podcastId);
|
|
69
|
+
const { ready, available } = await getMostRelevantLanguage(translationData);
|
|
70
|
+
|
|
71
|
+
// Retrieve transcription
|
|
72
|
+
const result = await transcriptionApi.getTranslation(podcastId, ready);
|
|
44
73
|
|
|
45
74
|
const arrayTranscript = parseSrt(result);
|
|
46
75
|
const actualText =
|
|
@@ -53,6 +82,26 @@ export const usePlayerTranscript = ()=>{
|
|
|
53
82
|
actualText: actualText,
|
|
54
83
|
value: arrayTranscript
|
|
55
84
|
});
|
|
85
|
+
|
|
86
|
+
if (available !== undefined && ready !== available) {
|
|
87
|
+
generatingTranscriptLanguage.value = available;
|
|
88
|
+
|
|
89
|
+
// If there's a better language available, trigger its generation
|
|
90
|
+
const result = await transcriptionApi.getTranslation(podcastId, available, true);
|
|
91
|
+
|
|
92
|
+
const arrayTranscript = parseSrt(result);
|
|
93
|
+
const actualText =
|
|
94
|
+
arrayTranscript?.[0]?.startTime === 0 ? arrayTranscript[0].text : "";
|
|
95
|
+
if(!arrayTranscript){
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
playerStore.playerUpdateTranscript({
|
|
99
|
+
actual: playerStore.playerTranscript?.actual,
|
|
100
|
+
actualText: actualText,
|
|
101
|
+
value: arrayTranscript
|
|
102
|
+
});
|
|
103
|
+
generatingTranscriptLanguage.value = null;
|
|
104
|
+
}
|
|
56
105
|
}
|
|
57
106
|
|
|
58
107
|
function parseSrt(transcript: string) {
|
|
@@ -110,11 +159,11 @@ export const usePlayerTranscript = ()=>{
|
|
|
110
159
|
}
|
|
111
160
|
}
|
|
112
161
|
|
|
113
|
-
|
|
114
|
-
return {
|
|
162
|
+
return {
|
|
115
163
|
checkDelaytWithStitching,
|
|
116
164
|
getTranscription,
|
|
117
165
|
onTimeUpdateTranscript,
|
|
118
|
-
onSeekedTranscript
|
|
119
|
-
|
|
166
|
+
onSeekedTranscript,
|
|
167
|
+
generatingTranscriptLanguage
|
|
168
|
+
}
|
|
120
169
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { Conference } from '@/stores/class/conference/conference';
|
|
2
2
|
import { Podcast } from '@/stores/class/general/podcast';
|
|
3
|
-
import dayjs from 'dayjs';
|
|
4
|
-
import duration from "dayjs/plugin/duration";
|
|
5
|
-
dayjs.extend(duration);
|
|
6
3
|
// @ts-expect-error Bibliothèque non typée
|
|
7
4
|
import humanizeDuration from "humanize-duration";
|
|
8
5
|
import {computed, Ref} from 'vue';
|
|
9
6
|
import { useI18n } from 'vue-i18n';
|
|
10
7
|
import {useOrgaComputed} from "../useOrgaComputed"
|
|
11
8
|
import { state } from '../../../stores/ParamSdkStore';
|
|
9
|
+
import { useDayjs } from '../useDayjs';
|
|
12
10
|
|
|
13
11
|
export const usePodcastView = (podcast: Ref<Podcast|undefined>, podcastConference: Ref<Conference|undefined>)=>{
|
|
14
12
|
|
|
15
13
|
const {locale} = useI18n();
|
|
14
|
+
const { dayjs } = useDayjs();
|
|
16
15
|
|
|
17
16
|
const {isEditRights, isPodcastmaker} = useOrgaComputed();
|
|
18
17
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OrganisationAttributes } from "
|
|
1
|
+
import { OrganisationAttributes } from "../../../stores/class/general/organisation";
|
|
2
2
|
import { RouteLocationAsRelativeTyped, useRouter } from "vue-router";
|
|
3
3
|
import { useSeoTitleUrl } from "../route/useSeoTitleUrl";
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ import RadioFranceIcon from "../../icons/RadioFranceIcon.vue";
|
|
|
14
14
|
import YoutubeIcon from "vue-material-design-icons/Youtube.vue";
|
|
15
15
|
import SpotifyIcon from "vue-material-design-icons/Spotify.vue";
|
|
16
16
|
import { Annotations } from "@/stores/class/general";
|
|
17
|
-
import { computed, type Component } from "vue";
|
|
17
|
+
import { computed, markRaw, type Component } from "vue";
|
|
18
18
|
|
|
19
19
|
export enum SharePlatformName {
|
|
20
20
|
APPLE = "applePodcast",
|
|
@@ -55,77 +55,77 @@ export const useSharePlatforms = () => {
|
|
|
55
55
|
const platforms = computed((): Array<SharePlatform> => {
|
|
56
56
|
return [{
|
|
57
57
|
name: SharePlatformName.APPLE,
|
|
58
|
-
icon: ApplePodcastIcon,
|
|
58
|
+
icon: markRaw(ApplePodcastIcon),
|
|
59
59
|
title: "Apple Podcast | iTunes",
|
|
60
60
|
color:"#aa1dd3"
|
|
61
61
|
}, {
|
|
62
62
|
name: SharePlatformName.DEEZER,
|
|
63
|
-
icon: DeezerIcon,
|
|
63
|
+
icon: markRaw(DeezerIcon),
|
|
64
64
|
title: "Deezer",
|
|
65
65
|
color:"#a238ff",
|
|
66
66
|
}, {
|
|
67
67
|
name: SharePlatformName.SPOTIFY,
|
|
68
|
-
icon: SpotifyIcon,
|
|
68
|
+
icon: markRaw(SpotifyIcon),
|
|
69
69
|
title: "Spotify",
|
|
70
70
|
color: "#1ed760",
|
|
71
71
|
}, {
|
|
72
72
|
name: SharePlatformName.AMAZON,
|
|
73
|
-
icon: AmazonMusicIcon,
|
|
73
|
+
icon: markRaw(AmazonMusicIcon),
|
|
74
74
|
title: "Amazon Music",
|
|
75
75
|
color: "#0c6cb3",
|
|
76
76
|
}, {
|
|
77
77
|
name: SharePlatformName.I_HEART,
|
|
78
|
-
icon: IHeartIcon,
|
|
78
|
+
icon: markRaw(IHeartIcon),
|
|
79
79
|
title: "iHeart",
|
|
80
80
|
color:"#e11b22"
|
|
81
81
|
}, {
|
|
82
82
|
name: SharePlatformName.PLAYER_FM,
|
|
83
|
-
icon: PlayerFmIcon,
|
|
83
|
+
icon: markRaw(PlayerFmIcon),
|
|
84
84
|
title: "Player FM",
|
|
85
85
|
color:"#bb202a"
|
|
86
86
|
}, {
|
|
87
87
|
name: SharePlatformName.POCKET_CASTS,
|
|
88
|
-
icon: PocketCastIcon,
|
|
88
|
+
icon: markRaw(PocketCastIcon),
|
|
89
89
|
title: "Pocket Casts",
|
|
90
90
|
color:"#f43e37"
|
|
91
91
|
}, {
|
|
92
92
|
name: SharePlatformName.PODCAST_ADDICT,
|
|
93
|
-
icon: PodcastAddictIcon,
|
|
93
|
+
icon: markRaw(PodcastAddictIcon),
|
|
94
94
|
title: "Podcast Addict",
|
|
95
95
|
color:"#f4842d"
|
|
96
96
|
}, {
|
|
97
97
|
name: SharePlatformName.RADIOLINE,
|
|
98
|
-
icon: RadiolineIcon,
|
|
98
|
+
icon: markRaw(RadiolineIcon),
|
|
99
99
|
title: "Radioline",
|
|
100
100
|
color:"#1678bd"
|
|
101
101
|
}, {
|
|
102
102
|
name: SharePlatformName.TUNE_IN,
|
|
103
|
-
icon: TuninIcon,
|
|
103
|
+
icon: markRaw(TuninIcon),
|
|
104
104
|
title: "TuneIn",
|
|
105
105
|
color:"#36b4a7"
|
|
106
106
|
}, {
|
|
107
107
|
name: SharePlatformName.YOUTUBE,
|
|
108
|
-
icon: YoutubeIcon,
|
|
108
|
+
icon: markRaw(YoutubeIcon),
|
|
109
109
|
title: "YouTube Music",
|
|
110
110
|
color: "#fe0000",
|
|
111
111
|
}, {
|
|
112
112
|
name: SharePlatformName.CASTBOX,
|
|
113
|
-
icon: CastboxIcon,
|
|
113
|
+
icon: markRaw(CastboxIcon),
|
|
114
114
|
title: "Castbox",
|
|
115
115
|
color: "#fe6222",
|
|
116
116
|
}, {
|
|
117
117
|
name: SharePlatformName.PODBEAN,
|
|
118
|
-
icon: PodbeanIcon,
|
|
118
|
+
icon: markRaw(PodbeanIcon),
|
|
119
119
|
title: "PodBean",
|
|
120
120
|
color: "#428200",
|
|
121
121
|
}, {
|
|
122
122
|
name: SharePlatformName.PODCAST_REPUBLIC,
|
|
123
|
-
icon: PodcastRepublicIcon,
|
|
123
|
+
icon: markRaw(PodcastRepublicIcon),
|
|
124
124
|
title: "Podcast Republic",
|
|
125
125
|
color: "#5c85dd",
|
|
126
126
|
}, {
|
|
127
127
|
name: SharePlatformName.RADIO_FRANCE,
|
|
128
|
-
icon: RadioFranceIcon,
|
|
128
|
+
icon: markRaw(RadioFranceIcon),
|
|
129
129
|
title: "Radio France",
|
|
130
130
|
color: "#a90041",
|
|
131
131
|
}];
|