Function bodies 195 total
main function · python · L211-L247 (37 LOC)diarize.py
def main():
args = parse_args()
# Provjera ulaznih datoteka
if not os.path.exists(args.wav):
print(f"❌ WAV datoteka ne postoji: {args.wav}")
sys.exit(1)
if not os.path.exists(args.srt):
print(f"❌ SRT datoteka ne postoji: {args.srt}")
sys.exit(1)
print(f" 📄 WAV: {os.path.basename(args.wav)}")
print(f" 📄 SRT: {os.path.basename(args.srt)}")
# 1. Parsiraj postojeći SRT (od whisper.cpp)
srt_segments = parse_srt(args.srt)
print(f" 📝 Parsirano {len(srt_segments)} SRT segmenata")
if len(srt_segments) == 0:
print("❌ SRT datoteka je prazna ili neispravan format!")
sys.exit(1)
# 2. Pokreni pyannote diarizaciju (na MPS/Metal GPU)
speaker_segments = run_diarization(
args.wav,
args.hf_token,
device=args.device,
min_speakers=args.min_speakers,
max_speakers=args.max_speakers
)
# 3. Pridruži govornika svakom SRT segmentu
srt_segments = assign_spsleep function · javascript · L69-L71 (3 LOC)fetch.js
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}sanitizeDescription function · javascript · L73-L84 (12 LOC)fetch.js
function sanitizeDescription(str) {
if (!str) return "nepoznat_naslov";
str = str.toLowerCase();
const map = {
'č': 'c', 'ć': 'c', 'ž': 'z', 'š': 's', 'đ': 'd',
'Č': 'c', 'Ć': 'c', 'Ž': 'z', 'Š': 's', 'Đ': 'd'
};
str = str.replace(/[čćžšđČĆŽŠĐ]/g, (char) => map[char] || char);
str = str.replace(/[^a-z0-9]/g, '_');
str = str.replace(/_+/g, '_').replace(/^_|_$/g, '');
return str || "nepoznat_naslov";
}extractVideoId function · javascript · L86-L91 (6 LOC)fetch.js
function extractVideoId(url) {
url = url.trim();
if (!url) return null;
const m = url.match(/(?:youtu\.be\/|v=)([a-zA-Z0-9_-]{11})/);
return m ? m[1] : null;
}extractDataFromLine function · javascript · L93-L110 (18 LOC)fetch.js
function extractDataFromLine(line) {
line = line.trim();
if (!line || line.startsWith("#")) return null;
if (line.includes("|")) {
const parts = line.split("|");
const url = parts[parts.length - 1].trim();
let title = "nepoznat_naslov";
let date = "NA";
if (parts.length >= 3) {
date = parts[0].trim();
title = parts.slice(1, parts.length - 1).join(" ").trim();
} else if (parts.length === 2) {
title = parts[0].trim();
}
return { url, title, date };
}
return { url: line, title: "nepoznat_naslov", date: "NA" };
}loadState function · javascript · L112-L124 (13 LOC)fetch.js
function loadState(stateFile) {
if (fs.existsSync(stateFile)) {
try {
const state = JSON.parse(fs.readFileSync(stateFile, "utf-8"));
// Osiguraj da 'private' polje postoji (kompatibilnost sa starijim state datotekama)
if (!Array.isArray(state.private)) state.private = [];
return state;
} catch (e) {
console.error(`[GREŠKA] Neispravan JSON stanja: ${stateFile}`);
}
}
return { completed: [], failed: [], private: [] };
}saveState function · javascript · L126-L130 (5 LOC)fetch.js
function saveState(stateFile, state) {
const tempFile = stateFile + ".tmp";
fs.writeFileSync(tempFile, JSON.stringify(state, null, 2));
fs.renameSync(tempFile, stateFile);
}Repobility — the code-quality scanner for AI-generated software · https://repobility.com
downloadVideo function · javascript · L132-L172 (41 LOC)fetch.js
function downloadVideo(videoId, outputDir, filenameTemplate) {
fs.mkdirSync(outputDir, { recursive: true });
const finalTemplate = path.join(outputDir, filenameTemplate + ".%(ext)s");
const args = [
...YT_DLP_BASE_ARGS,
"-o", finalTemplate,
`https://www.youtube.com/watch?v=${videoId}`,
];
return new Promise((resolve, reject) => {
// Capture stderr da detektiramo private/unavailable video greške
const proc = spawn("yt-dlp", args, {
stdio: ["inherit", "inherit", "pipe"] // stdin+stdout inherit, stderr capture
});
let stderrOutput = "";
proc.stderr.on("data", (chunk) => {
const text = chunk.toString();
stderrOutput += text;
// Ispiši stderr u realnom vremenu (kao i prije)
process.stderr.write(text);
});
proc.on("close", (code) => {
if (code === 0) {
resolve();
} else {
const err = new Error(`yt-dlp exit code: ${code}`);
// Detektiraj private/unavailable video
ifChannelQueue class · javascript · L174-L295 (122 LOC)fetch.js
class ChannelQueue {
constructor(filePath, baseOutputDir) {
this.filePath = filePath;
this.baseOutputDir = baseOutputDir;
const filename = path.basename(filePath).replace("-lista.txt", "").replace(".txt", "");
this.channelName = sanitizeDescription(filename);
this.outputDir = path.join(baseOutputDir, this.channelName);
this.stateFile = filePath.replace(".txt", "-state.json");
this.state = loadState(this.stateFile);
this.pendingVideos = [];
this.isExhausted = false;
this.init();
}
init() {
const rawLines = fs.readFileSync(this.filePath, "utf-8").split("\n");
const entries = rawLines
.map((line) => {
const data = extractDataFromLine(line);
if (!data) return null;
const videoId = extractVideoId(data.url);
if (!videoId) return null;
const safeTitle = sanitizeDescription(data.title);
let filenameTemplate = "";
if (data.date && /^\d{8}$/.test(data.date) && data.date !== "NA"constructor method · javascript · L175-L186 (12 LOC)fetch.js
constructor(filePath, baseOutputDir) {
this.filePath = filePath;
this.baseOutputDir = baseOutputDir;
const filename = path.basename(filePath).replace("-lista.txt", "").replace(".txt", "");
this.channelName = sanitizeDescription(filename);
this.outputDir = path.join(baseOutputDir, this.channelName);
this.stateFile = filePath.replace(".txt", "-state.json");
this.state = loadState(this.stateFile);
this.pendingVideos = [];
this.isExhausted = false;
this.init();
}init method · javascript · L188-L218 (31 LOC)fetch.js
init() {
const rawLines = fs.readFileSync(this.filePath, "utf-8").split("\n");
const entries = rawLines
.map((line) => {
const data = extractDataFromLine(line);
if (!data) return null;
const videoId = extractVideoId(data.url);
if (!videoId) return null;
const safeTitle = sanitizeDescription(data.title);
let filenameTemplate = "";
if (data.date && /^\d{8}$/.test(data.date) && data.date !== "NA") {
filenameTemplate = `${data.date}_${safeTitle}_yt_${videoId}`;
} else {
filenameTemplate = `%(upload_date)s_${safeTitle}_yt_${videoId}`;
}
return { line, url: data.url, videoId, title: data.title, filenameTemplate };
})
.filter((e) => e && e.videoId);
const uniqueMap = new Map();
entries.forEach((e) => uniqueMap.set(e.videoId, e));
this.pendingVideos = Array.from(uniqueMap.values()).filter(e =>
!this.state.completed.includes(e.videoId) &&
!tprocessBatch method · javascript · L220-L294 (75 LOC)fetch.js
async processBatch(batchSize) {
if (this.pendingVideos.length === 0) {
this.isExhausted = true;
return 0;
}
const batch = this.pendingVideos.slice(0, batchSize);
let successCount = 0;
console.log(`\n🔵 [${this.channelName.toUpperCase()}] Batch... (Preostalo: ${this.pendingVideos.length})`);
for (let i = 0; i < batch.length; i++) {
const video = batch[i];
const logName = video.filenameTemplate.startsWith("%")
? `[Auto-Date] ...${video.title.substring(0, 30)}...`
: video.filenameTemplate;
console.log(` ➡️ [${i + 1}/${batch.length}] Cilj: "${logName}"`);
try {
await downloadVideo(video.videoId, this.outputDir, video.filenameTemplate);
if (globalConsecutiveErrors > 0) {
console.log(` ✨ [OPORAVAK] Resetiram brojač grešaka.`);
}
globalConsecutiveErrors = 0;
this.state.completed.push(video.videoId);
if (this.state.failed.includes(video.videoId)) {
main function · javascript · L297-L326 (30 LOC)fetch.js
async function main() {
const args = process.argv.slice(2);
const outputDirIdx = args.indexOf("--output-dir");
const baseOutputDir = outputDirIdx !== -1 ? args[outputDirIdx + 1] : DEFAULT_OUTPUT_DIR;
if (!fs.existsSync(LISTS_DIR)) { console.error(`Nema direktorija: ${LISTS_DIR}`); process.exit(1); }
const listFiles = fs.readdirSync(LISTS_DIR).filter((f) => f.endsWith("-lista.txt")).map((f) => path.join(LISTS_DIR, f));
console.log("--- Inicijalizacija ---");
let channels = listFiles.map((file) => new ChannelQueue(file, baseOutputDir));
let activeChannels = channels.filter((c) => !c.isExhausted);
console.log(`\n🚀 POČETAK RADA (Brave on macOS + 360p Limit)`);
console.log(` 📂 Liste: ${LISTS_DIR}`);
console.log(` 🍪 Browser Source: ${BROWSER_NAME.toUpperCase()}`);
console.log(` 🎥 Video Quality: Max 360p`);
let round = 1;
while (activeChannels.length > 0) {
console.log(`\n=== KRUG ${round} (Aktivnih: ${activeChannels.length}) ===`);
for (let i = 0sleep function · javascript · L70-L72 (3 LOC)generate_article_aistudio.js
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}startElapsedTimer function · javascript · L74-L86 (13 LOC)generate_article_aistudio.js
function startElapsedTimer(prefix) {
const start = Date.now();
const timer = setInterval(() => {
const elapsed = ((Date.now() - start) / 1000).toFixed(0);
process.stderr.write(`\r ⏱ ${prefix} ${elapsed}s...`);
}, 1000);
return {
stop() {
clearInterval(timer);
process.stderr.write("\r" + " ".repeat(60) + "\r");
}
};
}Source: Repobility analyzer · https://repobility.com
parseArgs function · javascript · L88-L120 (33 LOC)generate_article_aistudio.js
function parseArgs() {
const args = process.argv.slice(2);
function getArg(name) {
const idx = args.indexOf(name);
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
}
const file = getArg("--file");
const geminiKeyArg = getArg("--gemini-key") || process.env.GEMINI_API_KEY || null;
if (!file) {
console.error("❌ Obavezan argument: --file <putanja_do_srt_datoteke>");
process.exit(1);
}
if (!fs.existsSync(file)) {
console.error(`❌ Datoteka ne postoji: ${file}`);
process.exit(1);
}
if (!geminiKeyArg) {
console.error("❌ Gemini API ključ nije pronađen (--gemini-key ili GEMINI_API_KEY env)!");
process.exit(1);
}
const geminiKeys = geminiKeyArg.split(",").map(k => k.trim()).filter(k => k.length > 0);
if (geminiKeys.length === 0) {
console.error("❌ Nevažeći format Gemini ključeva!");
process.exit(1);
}
return { file, geminiKeys };
}getArg function · javascript · L90-L93 (4 LOC)generate_article_aistudio.js
function getArg(name) {
const idx = args.indexOf(name);
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
}tryRepairMalformedJson function · javascript · L122-L161 (40 LOC)generate_article_aistudio.js
function tryRepairMalformedJson(text) {
let repaired = text;
const MAX_FIXES = 100;
for (let i = 0; i < MAX_FIXES; i++) {
try {
return JSON.parse(repaired);
} catch (e) {
const posMatch = e.message.match(/position (\d+)/);
if (!posMatch) return null;
const pos = parseInt(posMatch[1]);
if (e.message.includes("Expected ','") && repaired[pos] === ':') {
let k = pos - 1;
while (k >= 0 && /\s/.test(repaired[k])) k--;
if (k >= 0 && repaired[k] === '"') {
k--;
while (k >= 0 && repaired[k] !== '"') k--;
if (k >= 0) {
repaired = repaired.slice(0, k) + '{ ' + repaired.slice(k);
continue;
}
}
}
if (e.message.includes("Expected ','") && repaired[pos] === ']') {
repaired = repairedextractJsonFromText function · javascript · L163-L179 (17 LOC)generate_article_aistudio.js
function extractJsonFromText(text) {
let clean = text.trim();
clean = clean.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
clean = clean.trim();
try {
return JSON.parse(clean);
} catch (firstErr) {
const repaired = tryRepairMalformedJson(clean);
if (repaired !== null) {
console.error(` 🔧 JSON automatski popravljen (malformirani niz objekata).`);
return repaired;
}
console.error(` ⚠️ JSON parse error: ${firstErr.message}`);
console.error(` ⚠️ Raw response (first 500 chars): ${text.substring(0, 500)}`);
throw firstErr;
}
}callGemini function · javascript · L185-L271 (87 LOC)generate_article_aistudio.js
async function callGemini(systemPrompt, userMessage, apiKeys, label = "Gemini API poziv", rawSavePath = null) {
const payload = {
contents: [
{
role: "user",
parts: [{ text: userMessage }]
}
],
systemInstruction: {
role: "system",
parts: [{ text: systemPrompt }]
},
generationConfig: {
temperature: 0.2,
responseMimeType: "application/json"
}
};
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
const currentKey = apiKeys[currentKeyIndex];
currentKeyIndex = (currentKeyIndex + 1) % apiKeys.length;
const url = `${GEMINI_API_BASE}/${GEMINI_MODEL}:generateContent?key=${currentKey}`;
const timer = startElapsedTimer(`${label} (AI Studio, ključ ${currentKeyIndex === 0 ? apiKeys.length : currentKeyIndex}/${apiKeys.length}, pokušaj ${attempt}/${MAX_RETRIES})`);
try {
const rmain function · javascript · L275-L468 (194 LOC)generate_article_aistudio.js
async function main() {
const { file, geminiKeys } = parseArgs();
console.log("");
console.log("╔══════════════════════════════════════════════════╗");
console.log("║ 📰 GEMINI ARTICLE GENERATOR — AI STUDIO ║");
console.log("╚══════════════════════════════════════════════════╝");
console.log(` 📂 Datoteka: ${file}`);
console.log(` 🤖 Model: ${GEMINI_MODEL}`);
console.log(` 🌐 Endpoint: AI Studio (API key, naplaćuje karticu!)`);
console.log(` 🔑 Ključevi: ${geminiKeys.length} aktivnih API ključa/eva`);
console.log("");
const srtContent = fs.readFileSync(file, "utf-8");
const baseDir = path.dirname(file);
const basename = path.basename(file).replace(/\.(srt|txt)$/i, "");
const today = new Date();
const dateStr = today.toISOString().split('T')[0];
const outlinePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}.outline.json`);
const articlePath = path.join(baseDir, `${basename}_${dateStr}_$sleep function · javascript · L93-L95 (3 LOC)generate_article_gemini.js
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}startElapsedTimer function · javascript · L98-L110 (13 LOC)generate_article_gemini.js
function startElapsedTimer(prefix) {
const start = Date.now();
const timer = setInterval(() => {
const elapsed = ((Date.now() - start) / 1000).toFixed(0);
process.stderr.write(`\r ⏱ ${prefix} ${elapsed}s...`);
}, 1000);
return {
stop() {
clearInterval(timer);
process.stderr.write("\r" + " ".repeat(60) + "\r"); // očisti liniju
}
};
}All rows scored by the Repobility analyzer (https://repobility.com)
getAccessToken function · javascript · L113-L120 (8 LOC)generate_article_gemini.js
function getAccessToken() {
try {
return execSync("gcloud auth print-access-token", { encoding: "utf-8" }).trim();
} catch (err) {
console.error("❌ Ne mogu dohvatiti access token. Pokreni: gcloud auth login");
process.exit(1);
}
}parseArgs function · javascript · L122-L160 (39 LOC)generate_article_gemini.js
function parseArgs() {
const args = process.argv.slice(2);
function getArg(name) {
const idx = args.indexOf(name);
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
}
const file = getArg("--file");
const inputDir = getArg("--input-dir");
const channel = getArg("--channel");
const limit = getArg("--limit") ? parseInt(getArg("--limit"), 10) : null;
const dryRun = args.includes("--dry-run");
if (!file && !inputDir) {
console.error("❌ Obavezan argument: --file <putanja> ili --input-dir <putanja>");
console.error("");
console.error("Primjeri:");
console.error(" node generate_article_gemini.js --file /path/to/transcript.srt");
console.error(" node generate_article_gemini.js --input-dir /Volumes/DOMOVINA1TB/fetch_domovina_tv_output");
console.error(" node generate_article_gemini.js --input-dir ... --channel domovina_tv --limit 10");
console.error(" node generate_agetArg function · javascript · L124-L127 (4 LOC)generate_article_gemini.js
function getArg(name) {
const idx = args.indexOf(name);
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
}tryRepairMalformedJson function · javascript · L164-L208 (45 LOC)generate_article_gemini.js
function tryRepairMalformedJson(text) {
let repaired = text;
const MAX_FIXES = 100;
for (let i = 0; i < MAX_FIXES; i++) {
try {
return JSON.parse(repaired);
} catch (e) {
const posMatch = e.message.match(/position (\d+)/);
if (!posMatch) return null;
const pos = parseInt(posMatch[1]);
// Slučaj 1: Očekivano ',' ili ']' a na poziciji je ':'
// → goli key-value par u nizu, nedostaje '{' oko objekta
if (e.message.includes("Expected ','") && repaired[pos] === ':') {
// Pronađi otvarajući " imena ključa (preskoči whitespace i zatvarajući ")
let k = pos - 1;
while (k >= 0 && /\s/.test(repaired[k])) k--; // preskoči razmake između ključa i :
if (k >= 0 && repaired[k] === '"') {
k--; // preskoči zatvarajući "
while (k >= 0 && repaired[k] !== '"') k--; // pronađi otvarajući "
extractJsonFromText function · javascript · L211-L229 (19 LOC)generate_article_gemini.js
function extractJsonFromText(text) {
let clean = text.trim();
// Ukloni sve markdown code block wrappere (```json, ```, ili samo ```)
clean = clean.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
clean = clean.trim();
try {
return JSON.parse(clean);
} catch (firstErr) {
// Pokušaj automatski popraviti česte Gemini malformacije
const repaired = tryRepairMalformedJson(clean);
if (repaired !== null) {
console.error(` 🔧 JSON automatski popravljen (malformirani niz objekata).`);
return repaired;
}
console.error(` ⚠️ JSON parse error: ${firstErr.message}`);
console.error(` ⚠️ Raw response (first 500 chars): ${text.substring(0, 500)}`);
throw firstErr;
}
}saveBlockedMarker function · javascript · L239-L252 (14 LOC)generate_article_gemini.js
function saveBlockedMarker(srtPath, err) {
const baseDir = path.dirname(srtPath);
const base = path.basename(srtPath).replace(/\.srt$/, "");
const blockedPath = path.join(baseDir, `${base}.blocked.json`);
const blockedData = {
blocked_at: new Date().toISOString(),
model: GEMINI_MODEL,
reason: err.blockReason,
source_file: path.basename(srtPath),
raw_response: err.rawResponse
};
fs.writeFileSync(blockedPath, JSON.stringify(blockedData, null, 2), "utf-8");
return blockedPath;
}hasCompleteArticle function · javascript · L258-L282 (25 LOC)generate_article_gemini.js
function hasCompleteArticle(channelDir, srtFilename) {
const basename = srtFilename.replace(/\.(srt|txt)$/i, "");
const today = new Date().toISOString().split('T')[0];
const articlePath = path.join(channelDir, `${basename}_${today}_${GEMINI_MODEL}.article.json`);
if (!fs.existsSync(articlePath)) return false;
try {
const article = JSON.parse(fs.readFileSync(articlePath, "utf-8"));
if (!article.iterations || article.iterations.length === 0) return false;
// Provjeri postoji li outline za usporedbu broja iteracija
const outlinePath = path.join(channelDir, `${basename}_${today}_${GEMINI_MODEL}.outline.json`);
if (fs.existsSync(outlinePath)) {
const outline = JSON.parse(fs.readFileSync(outlinePath, "utf-8"));
const expectedCount = Array.isArray(outline) ? outline.length : (outline.iterations?.length || 0);
if (article.iterations.length < expectedCount) return false;
}
// Sve discoverPendingFiles function · javascript · L290-L326 (37 LOC)generate_article_gemini.js
function discoverPendingFiles(inputDir, channelFilter) {
const byChannel = new Map();
const entries = fs.readdirSync(inputDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
if (channelFilter && entry.name !== channelFilter) continue;
const channelName = entry.name;
const channelDir = path.join(inputDir, channelName);
const pending = [];
const files = fs.readdirSync(channelDir);
for (const file of files) {
if (!file.endsWith(DIARIZED_SRT_SUFFIX)) continue;
if (file.startsWith("._")) continue;
// Provjeri postoji li blocked marker (PROHIBITED_CONTENT itd.)
const base = file.replace(/\.srt$/, "");
const blockedPath = path.join(channelDir, `${base}.blocked.json`);
if (fs.existsSync(blockedPath)) continue;
if (!hasCompleteArticle(channelDir, file)) {
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
buildRoundRobinQueue function · javascript · L335-L354 (20 LOC)generate_article_gemini.js
function buildRoundRobinQueue(byChannel) {
const queue = [];
const channels = [...byChannel.keys()].sort();
let hasMore = true;
let roundIndex = 0;
while (hasMore) {
hasMore = false;
for (const ch of channels) {
const files = byChannel.get(ch);
if (roundIndex < files.length) {
queue.push({ srtPath: files[roundIndex], channel: ch });
hasMore = true;
}
}
roundIndex++;
}
return queue;
}getOrRefreshAccessToken function · javascript · L362-L370 (9 LOC)generate_article_gemini.js
function getOrRefreshAccessToken() {
const now = Date.now();
if (cachedAccessToken && now < tokenExpiry) {
return cachedAccessToken;
}
cachedAccessToken = getAccessToken();
tokenExpiry = now + 50 * 60 * 1000;
return cachedAccessToken;
}callGemini function · javascript · L372-L480 (109 LOC)generate_article_gemini.js
async function callGemini(systemPrompt, userMessage, label = "Gemini API poziv", rawSavePath = null) {
const payload = {
contents: [
{
role: "user",
parts: [{ text: userMessage }]
}
],
systemInstruction: {
role: "system",
parts: [{ text: systemPrompt }]
},
generationConfig: {
temperature: 0.2,
responseMimeType: "application/json"
}
};
const url = `${GEMINI_API_BASE}/${GEMINI_MODEL}:generateContent`;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
const token = getOrRefreshAccessToken();
const timer = startElapsedTimer(`${label} (Vertex AI, pokušaj ${attempt}/${MAX_RETRIES})`);
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `BeaprocessFile function · javascript · L489-L686 (198 LOC)generate_article_gemini.js
async function processFile(file, { exitOnError = true } = {}) {
const srtContent = fs.readFileSync(file, "utf-8");
const baseDir = path.dirname(file);
const basename = path.basename(file).replace(/\.(srt|txt)$/i, "");
const today = new Date();
const dateStr = today.toISOString().split('T')[0];
const outlinePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}.outline.json`);
const articlePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}.article.json`);
const rawDir = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}_raw`);
if (!fs.existsSync(rawDir)) fs.mkdirSync(rawDir, { recursive: true });
// --- FAZA 1: OUTLINE ---
let outlineJson = null;
if (fs.existsSync(outlinePath)) {
console.log(` ✅ [FAZA 1] Pronađen postojeći outline: ${path.basename(outlinePath)}`);
outlineJson = JSON.parse(fs.readFileSync(outlinePath, "utf-8"));
} else {
const rawPath1 = path.join(rawDir, "main function · javascript · L690-L772 (83 LOC)generate_article_gemini.js
async function main() {
const opts = parseArgs();
console.log("");
console.log("╔══════════════════════════════════════════════════╗");
console.log("║ 📰 GEMINI ARTICLE GENERATOR (2 FAZE) ║");
console.log("╚══════════════════════════════════════════════════╝");
console.log(` 🤖 Model: ${GEMINI_MODEL}`);
console.log(` 🌐 Vertex AI: ${VERTEX_PROJECT} (${VERTEX_REGION})`);
// ── Način 1: Pojedinačna datoteka ──
if (opts.mode === "single") {
console.log(` 📂 Datoteka: ${opts.file}`);
console.log("");
await processFile(opts.file, { exitOnError: true });
console.log("");
return;
}
// ── Način 2: Batch s round-robin rasporedom ──
const { inputDir, channel, limit, dryRun } = opts;
console.log(` 📂 Input: ${inputDir}`);
if (channel) console.log(` 🎯 Kanal: ${channel}`);
if (limit) console.log(` 🔢 Limit: ${limit}`);
if (dryRun) console.log(" ⚠️ DRY RUN — samosleep function · javascript · L68-L70 (3 LOC)generate_article_vertexai_express.js
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}startElapsedTimer function · javascript · L72-L84 (13 LOC)generate_article_vertexai_express.js
function startElapsedTimer(prefix) {
const start = Date.now();
const timer = setInterval(() => {
const elapsed = ((Date.now() - start) / 1000).toFixed(0);
process.stderr.write(`\r ⏱ ${prefix} ${elapsed}s...`);
}, 1000);
return {
stop() {
clearInterval(timer);
process.stderr.write("\r" + " ".repeat(60) + "\r");
}
};
}parseArgs function · javascript · L86-L123 (38 LOC)generate_article_vertexai_express.js
function parseArgs() {
const args = process.argv.slice(2);
function getArg(name) {
const idx = args.indexOf(name);
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
}
const file = getArg("--file");
const geminiKeyArg = getArg("--gemini-key") || process.env.GEMINI_API_KEY || null;
if (!file) {
console.error("❌ Obavezan argument: --file <putanja_do_srt_datoteke>");
process.exit(1);
}
if (!fs.existsSync(file)) {
console.error(`❌ Datoteka ne postoji: ${file}`);
process.exit(1);
}
if (!geminiKeyArg) {
console.error("❌ Gemini API ključ nije pronađen (--gemini-key ili GEMINI_API_KEY env)!");
process.exit(1);
}
const geminiKeys = geminiKeyArg.split(",").map(k => k.trim()).filter(k => k.length > 0);
if (geminiKeys.length === 0) {
console.error("❌ Nevažeći format Gemini ključeva!");
process.exit(1);
}
const model = getArg("--model");
Repobility — the code-quality scanner for AI-generated software · https://repobility.com
getArg function · javascript · L88-L91 (4 LOC)generate_article_vertexai_express.js
function getArg(name) {
const idx = args.indexOf(name);
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
}tryRepairMalformedJson function · javascript · L125-L164 (40 LOC)generate_article_vertexai_express.js
function tryRepairMalformedJson(text) {
let repaired = text;
const MAX_FIXES = 100;
for (let i = 0; i < MAX_FIXES; i++) {
try {
return JSON.parse(repaired);
} catch (e) {
const posMatch = e.message.match(/position (\d+)/);
if (!posMatch) return null;
const pos = parseInt(posMatch[1]);
if (e.message.includes("Expected ','") && repaired[pos] === ':') {
let k = pos - 1;
while (k >= 0 && /\s/.test(repaired[k])) k--;
if (k >= 0 && repaired[k] === '"') {
k--;
while (k >= 0 && repaired[k] !== '"') k--;
if (k >= 0) {
repaired = repaired.slice(0, k) + '{ ' + repaired.slice(k);
continue;
}
}
}
if (e.message.includes("Expected ','") && repaired[pos] === ']') {
repaired = repairedextractJsonFromText function · javascript · L166-L182 (17 LOC)generate_article_vertexai_express.js
function extractJsonFromText(text) {
let clean = text.trim();
clean = clean.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
clean = clean.trim();
try {
return JSON.parse(clean);
} catch (firstErr) {
const repaired = tryRepairMalformedJson(clean);
if (repaired !== null) {
console.error(` 🔧 JSON automatski popravljen (malformirani niz objekata).`);
return repaired;
}
console.error(` ⚠️ JSON parse error: ${firstErr.message}`);
console.error(` ⚠️ Raw response (first 500 chars): ${text.substring(0, 500)}`);
throw firstErr;
}
}callGemini function · javascript · L188-L274 (87 LOC)generate_article_vertexai_express.js
async function callGemini(systemPrompt, userMessage, apiKeys, label = "Gemini API poziv", rawSavePath = null) {
const payload = {
contents: [
{
role: "user",
parts: [{ text: userMessage }]
}
],
systemInstruction: {
role: "system",
parts: [{ text: systemPrompt }]
},
generationConfig: {
temperature: 0.2,
responseMimeType: "application/json"
}
};
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
const currentKey = apiKeys[currentKeyIndex];
currentKeyIndex = (currentKeyIndex + 1) % apiKeys.length;
const url = `${GEMINI_API_BASE}/${GEMINI_MODEL}:generateContent?key=${currentKey}`;
const timer = startElapsedTimer(`${label} (Vertex Express, ključ ${currentKeyIndex === 0 ? apiKeys.length : currentKeyIndex}/${apiKeys.length}, pokušaj ${attempt}/${MAX_RETRIES})`);
try {
comain function · javascript · L278-L471 (194 LOC)generate_article_vertexai_express.js
async function main() {
const { file, geminiKeys } = parseArgs();
console.log("");
console.log("╔══════════════════════════════════════════════════╗");
console.log("║ 📰 GEMINI ARTICLE GENERATOR — VERTEX EXPRESS ║");
console.log("╚══════════════════════════════════════════════════╝");
console.log(` 📂 Datoteka: ${file}`);
console.log(` 🤖 Model: ${GEMINI_MODEL}`);
console.log(` 🌐 Endpoint: Vertex AI Express (API key)`);
console.log(` 🔑 Ključevi: ${geminiKeys.length} aktivnih API ključa/eva`);
console.log("");
const srtContent = fs.readFileSync(file, "utf-8");
const baseDir = path.dirname(file);
const basename = path.basename(file).replace(/\.(srt|txt)$/i, "");
const today = new Date();
const dateStr = today.toISOString().split('T')[0];
const outlinePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}.outline.json`);
const articlePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODsanitizeDescription function · javascript · L53-L64 (12 LOC)generate_whisper_prompt.js
function sanitizeDescription(str) {
if (!str) return "nepoznat_naslov";
str = str.toLowerCase();
const map = {
'č': 'c', 'ć': 'c', 'ž': 'z', 'š': 's', 'đ': 'd',
'Č': 'c', 'Ć': 'c', 'Ž': 'z', 'Š': 's', 'Đ': 'd'
};
str = str.replace(/[čćžšđČĆŽŠĐ]/g, (char) => map[char] || char);
str = str.replace(/[^a-z0-9]/g, '_');
str = str.replace(/_+/g, '_').replace(/^_|_$/g, '');
return str || "nepoznat_naslov";
}extractVideoId function · javascript · L66-L71 (6 LOC)generate_whisper_prompt.js
function extractVideoId(url) {
url = url.trim();
if (!url) return null;
const m = url.match(/(?:youtu\.be\/|v=)([a-zA-Z0-9_-]{11})/);
return m ? m[1] : null;
}extractDataFromLine function · javascript · L73-L90 (18 LOC)generate_whisper_prompt.js
function extractDataFromLine(line) {
line = line.trim();
if (!line || line.startsWith("#")) return null;
if (line.includes("|")) {
const parts = line.split("|");
const url = parts[parts.length - 1].trim();
let title = "nepoznat_naslov";
let date = "NA";
if (parts.length >= 3) {
date = parts[0].trim();
title = parts.slice(1, parts.length - 1).join(" ").trim();
} else if (parts.length === 2) {
title = parts[0].trim();
}
return { url, title, date };
}
return { url: line, title: "nepoznat_naslov", date: "NA" };
}Source: Repobility analyzer · https://repobility.com
loadState function · javascript · L92-L101 (10 LOC)generate_whisper_prompt.js
function loadState(stateFile) {
if (fs.existsSync(stateFile)) {
try {
return JSON.parse(fs.readFileSync(stateFile, "utf-8"));
} catch (e) {
console.error(`[GREŠKA] Neispravan JSON stanja: ${stateFile}`);
}
}
return { completed: [], failed: [] };
}callLLM function · javascript · L108-L162 (55 LOC)generate_whisper_prompt.js
function callLLM(title, description) {
const userMessage = `NASLOV: ${title}\n\nOPIS:\n${description}`;
const payload = JSON.stringify({
model: LM_STUDIO_MODEL,
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: userMessage }
],
temperature: 0.0,
max_tokens: 300
});
return new Promise((resolve, reject) => {
const url = new URL(LM_STUDIO_URL);
const options = {
hostname: url.hostname,
port: url.port,
path: url.pathname,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload)
}
};
const req = http.request(options, (res) => {
let data = "";
res.on("data", (chunk) => { data += chunk; });
res.on("end", () => {
try {
const jssanitizeLLMOutput function · javascript · L167-L192 (26 LOC)generate_whisper_prompt.js
function sanitizeLLMOutput(raw) {
// Makni sve što nije dio ključnih riječi (LLM ponekad doda uvodni tekst)
let cleaned = raw.trim();
// Ako ima više linija, uzmi samo prvu koja izgleda kao lista ključnih riječi
const lines = cleaned.split('\n').map(l => l.trim()).filter(l => l.length > 0);
for (const line of lines) {
// Linija koja sadrži zareze i nema razmaka nakon zareza izgleda kao ispravan output
if (line.includes(',') && !line.includes(': ')) {
cleaned = line;
break;
}
}
// Zamijeni eventualne razmake nakon zareza
cleaned = cleaned.replace(/,\s+/g, ',');
// Zamijeni eventualne razmake unutar pojmova podcrtom
// (LLM ponekad zaboravi pravilo o podcrti)
const parts = cleaned.split(',');
const fixed = parts
.map(p => p.trim().replace(/\s+/g, '_'))
.filter(p => p.length >= 2);
return fixed.join(',');
}